From b28a385fd3bee7c4966d03d7fb8422d33972a19a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 5 Feb 2024 09:23:34 +0100 Subject: [PATCH 001/245] Add rollout for cookie popup management enabled by default (#2373) --- Core/FeatureFlag.swift | 3 + Core/PixelEvent.swift | 6 - Core/UserDefaultsPropertyWrapper.swift | 1 - DuckDuckGo.xcodeproj/project.pbxproj | 44 ----- DuckDuckGo/AppSettings.swift | 1 - DuckDuckGo/AppUserDefaults.swift | 28 ++- .../Autoconsent/AutoconsentManagement.swift | 1 - .../Autoconsent/AutoconsentUserScript.swift | 58 +----- .../AutoconsentSettingsViewController.swift | 1 - .../CookieConsentDaxDialogViewModel.swift | 43 ----- DuckDuckGo/CustomDaxDialog.swift | 181 ------------------ DuckDuckGo/CustomDaxDialogViewModel.swift | 52 ----- DuckDuckGo/RootDebugViewController.swift | 3 +- DuckDuckGo/TabViewController.swift | 32 ---- DuckDuckGo/UserText.swift | 8 - ...kie-banner-illustration-animated-dark.json | 1 - .../cookie-banner-illustration-animated.json | 1 - DuckDuckGo/en.lproj/Localizable.strings | 12 -- DuckDuckGoTests/AppSettingsMock.swift | 1 - DuckDuckGoTests/AppUserDefaultsTests.swift | 40 +++- .../AutoconsentMessageProtocolTests.swift | 1 - .../AutoconsentBackgroundTests.swift | 1 - 22 files changed, 74 insertions(+), 445 deletions(-) delete mode 100644 DuckDuckGo/CookieConsentDaxDialogViewModel.swift delete mode 100644 DuckDuckGo/CustomDaxDialog.swift delete mode 100644 DuckDuckGo/CustomDaxDialogViewModel.swift delete mode 100644 DuckDuckGo/cookie-banner-illustration-animated-dark.json delete mode 100644 DuckDuckGo/cookie-banner-illustration-animated.json diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index f5c96b04ad..e7e9b9165b 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -35,6 +35,7 @@ public enum FeatureFlag: String { case networkProtectionWaitlistAccess case networkProtectionWaitlistActive case subscription + case autoconsentOnByDefault } extension FeatureFlag: FeatureFlagSourceProviding { @@ -64,6 +65,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault)) case .incontextSignup: return .remoteReleasable(.feature(.incontextSignup)) + case .autoconsentOnByDefault: + return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault)) } } } diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index babb0b9179..a94d9a0693 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -119,9 +119,6 @@ extension Pixel { case daxDialogsFireEducationShown case daxDialogsFireEducationConfirmed case daxDialogsFireEducationCancelled - case daxDialogsAutoconsentShown - case daxDialogsAutoconsentConfirmed - case daxDialogsAutoconsentCancelled case defaultBrowserButtonPressedSettings @@ -624,9 +621,6 @@ extension Pixel.Event { case .daxDialogsFireEducationShown: return "m_dx_fe_s" case .daxDialogsFireEducationConfirmed: return "m_dx_fe_co" case .daxDialogsFireEducationCancelled: return "m_dx_fe_ca" - case .daxDialogsAutoconsentShown: return "m_dax_dialog_autoconsent_shown" - case .daxDialogsAutoconsentConfirmed: return "m_dax_dialog_autoconsent_confirmed" - case .daxDialogsAutoconsentCancelled: return "m_dax_dialog_autoconsent_cancelled" case .defaultBrowserButtonPressedSettings: return "m_db_s" diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 850a3a9738..bf12cc003f 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -87,7 +87,6 @@ public struct UserDefaultsWrapper { case voiceSearchEnabled = "com.duckduckgo.app.voiceSearchEnabled" - case autoconsentPromptSeen = "com.duckduckgo.ios.autoconsentPromptSeen" case autoconsentEnabled = "com.duckduckgo.ios.autoconsentEnabled" case shouldScheduleRulesCompilationOnAppLaunch = "com.duckduckgo.ios.shouldScheduleRulesCompilationOnAppLaunch" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5ba21aebd7..700b74eb7b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -105,17 +105,12 @@ 1CB7B82123CEA1F800AA24EA /* DateExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */; }; 1CB7B82323CEA28300AA24EA /* DateExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */; }; 1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E016AB32949FEB500F21625 /* OmniBarNotificationViewModel.swift */; }; - 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E016AB5294A5EB100F21625 /* CustomDaxDialog.swift */; }; 1E05D1D629C46EBB00BF9A1F /* DailyPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */; }; 1E05D1D829C46EDA00BF9A1F /* TimedPixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */; }; 1E05D1DB29C47B3300BF9A1F /* DailyPixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E05D1D929C47B2B00BF9A1F /* DailyPixelTests.swift */; }; 1E0A75EA27A2FBD000A2BFB6 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 1E0A75E927A2FBD000A2BFB6 /* Downloads.storyboard */; }; 1E162605296840D80004127F /* Triangle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E162604296840D80004127F /* Triangle.swift */; }; 1E1626072968413B0004127F /* ViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1626062968413B0004127F /* ViewExtension.swift */; }; - 1E16260B296845120004127F /* cookie-banner-illustration-animated.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E162609296845120004127F /* cookie-banner-illustration-animated.json */; }; - 1E16260C296845120004127F /* cookie-banner-illustration-animated-dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E16260A296845120004127F /* cookie-banner-illustration-animated-dark.json */; }; - 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E16260F296C5C630004127F /* CustomDaxDialogViewModel.swift */; }; - 1E162613296C62820004127F /* CookieConsentDaxDialogViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E162612296C62820004127F /* CookieConsentDaxDialogViewModel.swift */; }; 1E162615296D910F0004127F /* cookie-icon-animated-40-dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E162614296D910F0004127F /* cookie-icon-animated-40-dark.json */; }; 1E1D8B5D2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1D8B5C2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift */; }; 1E1D8B6129950FD200C96994 /* AutoconsentBackgroundTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E1D8B6029950FD200C96994 /* AutoconsentBackgroundTests.swift */; }; @@ -1210,17 +1205,12 @@ 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtension.swift; sourceTree = ""; }; 1CB7B82223CEA28300AA24EA /* DateExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensionTests.swift; sourceTree = ""; }; 1E016AB32949FEB500F21625 /* OmniBarNotificationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OmniBarNotificationViewModel.swift; sourceTree = ""; }; - 1E016AB5294A5EB100F21625 /* CustomDaxDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDaxDialog.swift; sourceTree = ""; }; 1E05D1D529C46EBB00BF9A1F /* DailyPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixel.swift; sourceTree = ""; }; 1E05D1D729C46EDA00BF9A1F /* TimedPixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimedPixel.swift; sourceTree = ""; }; 1E05D1D929C47B2B00BF9A1F /* DailyPixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyPixelTests.swift; sourceTree = ""; }; 1E0A75E927A2FBD000A2BFB6 /* Downloads.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Downloads.storyboard; sourceTree = ""; }; 1E162604296840D80004127F /* Triangle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Triangle.swift; sourceTree = ""; }; 1E1626062968413B0004127F /* ViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtension.swift; sourceTree = ""; }; - 1E162609296845120004127F /* cookie-banner-illustration-animated.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-banner-illustration-animated.json"; sourceTree = ""; }; - 1E16260A296845120004127F /* cookie-banner-illustration-animated-dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-banner-illustration-animated-dark.json"; sourceTree = ""; }; - 1E16260F296C5C630004127F /* CustomDaxDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDaxDialogViewModel.swift; sourceTree = ""; }; - 1E162612296C62820004127F /* CookieConsentDaxDialogViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieConsentDaxDialogViewModel.swift; sourceTree = ""; }; 1E162614296D910F0004127F /* cookie-icon-animated-40-dark.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-icon-animated-40-dark.json"; sourceTree = ""; }; 1E1D8B5C2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoconsentMessageProtocolTests.swift; sourceTree = ""; }; 1E1D8B6029950FD200C96994 /* AutoconsentBackgroundTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoconsentBackgroundTests.swift; sourceTree = ""; }; @@ -3095,16 +3085,6 @@ name = AppTPBreakageForm; sourceTree = ""; }; - 1E16260029683B4D0004127F /* CustomDaxDialog */ = { - isa = PBXGroup; - children = ( - 1E1626082968418F0004127F /* Animations */, - 1E162611296C62350004127F /* Model */, - 1E016AB5294A5EB100F21625 /* CustomDaxDialog.swift */, - ); - name = CustomDaxDialog; - sourceTree = ""; - }; 1E162603296840790004127F /* SwiftUI */ = { isa = PBXGroup; children = ( @@ -3116,24 +3096,6 @@ name = SwiftUI; sourceTree = ""; }; - 1E1626082968418F0004127F /* Animations */ = { - isa = PBXGroup; - children = ( - 1E16260A296845120004127F /* cookie-banner-illustration-animated-dark.json */, - 1E162609296845120004127F /* cookie-banner-illustration-animated.json */, - ); - name = Animations; - sourceTree = ""; - }; - 1E162611296C62350004127F /* Model */ = { - isa = PBXGroup; - children = ( - 1E16260F296C5C630004127F /* CustomDaxDialogViewModel.swift */, - 1E162612296C62820004127F /* CookieConsentDaxDialogViewModel.swift */, - ); - name = Model; - sourceTree = ""; - }; 1E162616296D962A0004127F /* Model */ = { isa = PBXGroup; children = ( @@ -4941,7 +4903,6 @@ F11CEF581EBB66C80088E4D7 /* Tutorials */ = { isa = PBXGroup; children = ( - 1E16260029683B4D0004127F /* CustomDaxDialog */, 858650CF2469BCC100C36F8A /* DaxOnboarding */, 85EE7F53224667C3000FE757 /* WebContainer */, 85C11E4A209084DE00BFFEB4 /* HomeRow */, @@ -6085,7 +6046,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1E16260C296845120004127F /* cookie-banner-illustration-animated-dark.json in Resources */, AA4D6A8D23DE49A5007E8790 /* AppIconBlack40x40@3x.png in Resources */, F47E53DB250A9A1C0037C686 /* Onboarding.xcassets in Resources */, AA4D6ACC23DE4D27007E8790 /* AppIconPurple60x60@2x.png in Resources */, @@ -6148,7 +6108,6 @@ 85A313972028E78A00327D00 /* release_notes.txt in Resources */, 9865DFFD22A84CF300D27829 /* FavoriteHomeCell.xib in Resources */, 1EE411FE2858B9300003FE64 /* dark-shield.json in Resources */, - 1E16260B296845120004127F /* cookie-banner-illustration-animated.json in Resources */, AA4D6AD323DE4D27007E8790 /* AppIconPurple29x29@2x.png in Resources */, AA4D6AA123DE4CC4007E8790 /* AppIconBlue60x60@3x.png in Resources */, 984147A824F0259000362052 /* Onboarding.storyboard in Resources */, @@ -6531,7 +6490,6 @@ D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, - 1E162610296C5C630004127F /* CustomDaxDialogViewModel.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, @@ -6769,7 +6727,6 @@ 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, - 1E016AB6294A5EB100F21625 /* CustomDaxDialog.swift in Sources */, 02341FA42A437999008A1531 /* OnboardingStepView.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */, @@ -6958,7 +6915,6 @@ 3132FA2827A0788400DD7A12 /* PassKitPreviewHelper.swift in Sources */, 8505836C219F424500ED4EDB /* TextFieldWithInsets.swift in Sources */, CBD4F13F279EBFAF00B20FD7 /* HomeMessageViewModel.swift in Sources */, - 1E162613296C62820004127F /* CookieConsentDaxDialogViewModel.swift in Sources */, 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */, 2DC3FC65C6D9DA634426672D /* AutofillNoAuthAvailableView.swift in Sources */, ); diff --git a/DuckDuckGo/AppSettings.swift b/DuckDuckGo/AppSettings.swift index 790e10f995..b440082ffd 100644 --- a/DuckDuckGo/AppSettings.swift +++ b/DuckDuckGo/AppSettings.swift @@ -71,7 +71,6 @@ protocol AppSettings: AnyObject { func isWidgetInstalled() async -> Bool - var autoconsentPromptSeen: Bool { get set } var autoconsentEnabled: Bool { get set } var isSyncBookmarksPaused: Bool { get } diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 552e381a02..008facdf6a 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -287,12 +287,30 @@ public class AppUserDefaults: AppSettings { } } } - - @UserDefaultsWrapper(key: .autoconsentPromptSeen, defaultValue: false) - var autoconsentPromptSeen: Bool - + + var autoconsentEnabled: Bool { + get { + // Use settings value if present + if let isEnabled = autoconsentEnabledSetting { + return isEnabled + } + + // Use onByDefault rollout otherwise + return featureFlagger.isFeatureOn(.autoconsentOnByDefault) + } + + set { + autoconsentEnabledSetting = newValue + } + } + + // Only for testing and `DebugViewController` purposes + func clearAutoconsentUserSetting() { + autoconsentEnabledSetting = nil + } + @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: false) - var autoconsentEnabled: Bool + private var autoconsentEnabledSetting: Bool? var inspectableWebViewEnabled: Bool { get { diff --git a/DuckDuckGo/Autoconsent/AutoconsentManagement.swift b/DuckDuckGo/Autoconsent/AutoconsentManagement.swift index 524fa1792e..798431f0d3 100644 --- a/DuckDuckGo/Autoconsent/AutoconsentManagement.swift +++ b/DuckDuckGo/Autoconsent/AutoconsentManagement.swift @@ -25,7 +25,6 @@ final class AutoconsentManagement { private init() {} var sitesNotifiedCache = Set() - var promptLastShown: Date? func clearCache() { dispatchPrecondition(condition: .onQueue(.main)) diff --git a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift index d4b5858497..bc85b57395 100644 --- a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift +++ b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift @@ -27,7 +27,6 @@ import PrivacyDashboard // swiftlint:disable file_length protocol AutoconsentPreferences { - var autoconsentPromptSeen: Bool { get set } var autoconsentEnabled: Bool { get set } } @@ -35,7 +34,6 @@ extension AppUserDefaults: AutoconsentPreferences { } protocol AutoconsentUserScriptDelegate: AnyObject { func autoconsentUserScript(_ script: AutoconsentUserScript, didUpdateCookieConsentStatus cookieConsentStatus: CookieConsentInfo) - func autoconsentUserScript(_ script: AutoconsentUserScript, didRequestAskingUserForConsent completion: @escaping (Bool) -> Void) } protocol UserScriptWithAutoconsent: UserScript { @@ -211,6 +209,12 @@ extension AutoconsentUserScript { } } + @MainActor + func handlePopupFound(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { + os_log("Autoconsent popup found", log: .autoconsentLog) + replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection + } + @MainActor func handleInit(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { guard let messageData: InitMessage = decodeMessageBody(from: message.body) else { @@ -229,7 +233,7 @@ extension AutoconsentUserScript { return } - if preferences.autoconsentPromptSeen == true && preferences.autoconsentEnabled == false { + if preferences.autoconsentEnabled == 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 @@ -260,11 +264,9 @@ extension AutoconsentUserScript { "rules": nil, // rules are bundled with the content script atm "config": [ "enabled": true, - // if it's the first time, disable autoAction - "autoAction": preferences.autoconsentPromptSeen ? "optOut" : nil, + "autoAction": "optOut", "disabledCmps": disabledCMPs, - // the very first time (autoconsentEnabled = nil), make sure the popup is visible - "enablePrehide": preferences.autoconsentPromptSeen, + "enablePrehide": true, "enableCosmeticRules": true, "detectRetries": 20, "isMainWorld": false @@ -309,31 +311,7 @@ extension AutoconsentUserScript { replyHandler(nil, "missing frame target") } } - - @MainActor - func handlePopupFound(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { - guard preferences.autoconsentPromptSeen == false else { - // if feature is already enabled, opt-out will happen automatically - replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection - return - } - - os_log("Prompting user about autoconsent", log: .autoconsentLog, type: .debug) - // if it's the first time, prompt the user and trigger opt-out - if message.webView?.window != nil { - ensurePrompt(callback: { shouldProceed in - if shouldProceed { - Task { - replyHandler([ "type": "optOut" ], nil) - } - } - }) - } else { - replyHandler(nil, "missing frame target") - } - } - @MainActor func handleOptOutResult(message: WKScriptMessage, replyHandler: @escaping (Any?, String?) -> Void) { guard let messageData: OptOutResultMessage = decodeMessageBody(from: message.body) else { @@ -419,24 +397,6 @@ extension AutoconsentUserScript { refreshDashboardState(consentManaged: true, cosmetic: nil, optoutFailed: false, selftestFailed: messageData.result) replyHandler([ "type": "ok" ], nil) // this is just to prevent a Promise rejection } - - @MainActor - func ensurePrompt(callback: @escaping (Bool) -> Void) { - let now = Date.init() - guard management.promptLastShown == nil || now > management.promptLastShown!.addingTimeInterval(1) else { - // user said "not now" recently, don't bother asking - os_log("Have a recent user response, canceling prompt", log: .autoconsentLog, type: .debug) - callback(preferences.autoconsentEnabled) // if two prompts were scheduled from the same tab, result could be true - return - } - - management.promptLastShown = now - self.delegate?.autoconsentUserScript(self, didRequestAskingUserForConsent: { result in - self.preferences.autoconsentEnabled = result - self.preferences.autoconsentPromptSeen = true - callback(result) - }) - } } extension NSNotification.Name { diff --git a/DuckDuckGo/AutoconsentSettingsViewController.swift b/DuckDuckGo/AutoconsentSettingsViewController.swift index d4a65fd3cc..172be442e2 100644 --- a/DuckDuckGo/AutoconsentSettingsViewController.swift +++ b/DuckDuckGo/AutoconsentSettingsViewController.swift @@ -68,7 +68,6 @@ final class AutoconsentSettingsViewController: UITableViewController { @IBAction private func onAutoconsentValueChanged(_ sender: Any) { appSettings.autoconsentEnabled = autoconsentToggle.isOn - appSettings.autoconsentPromptSeen = true Pixel.fire(pixel: autoconsentToggle.isOn ? .settingsAutoconsentOn : .settingsAutoconsentOff) } diff --git a/DuckDuckGo/CookieConsentDaxDialogViewModel.swift b/DuckDuckGo/CookieConsentDaxDialogViewModel.swift deleted file mode 100644 index a79fe5c023..0000000000 --- a/DuckDuckGo/CookieConsentDaxDialogViewModel.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// CookieConsentDaxDialogViewModel.swift -// DuckDuckGo -// -// Copyright © 2023 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 - -final class CookieConsentDaxDialogViewModel: CustomDaxDialogViewModel { - - let okAction: () -> Void - let noAction: () -> Void - - init(okAction: @escaping () -> Void, noAction: @escaping () -> Void) { - self.okAction = okAction - self.noAction = noAction - } - - lazy var content: [DialogContentItem] = [.text(text: UserText.daxDialogCookieConsentFirst), - .animation(name: cookieBannerAnimationName, delay: 0.35), - .text(text: UserText.daxDialogCookieConsentSecond)] - - lazy var buttons: [DialogButtonItem] = [.bordered(label: UserText.daxDialogCookieConsentAcceptButton, action: self.okAction), - .borderless(label: UserText.daxDialogCookieConsentRejectButton, action: self.noAction)] - - private var cookieBannerAnimationName: String { - let useLightStyle = ThemeManager.shared.currentTheme.currentImageSet == .light - return useLightStyle ? "cookie-banner-illustration-animated" : "cookie-banner-illustration-animated-dark" - } -} diff --git a/DuckDuckGo/CustomDaxDialog.swift b/DuckDuckGo/CustomDaxDialog.swift deleted file mode 100644 index f8b71fae4d..0000000000 --- a/DuckDuckGo/CustomDaxDialog.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// CustomDaxDialog.swift -// DuckDuckGo -// -// Copyright © 2022 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 CustomDaxDialog: View { - - @Environment(\.horizontalSizeClass) var horizontalSizeClass - @Environment(\.verticalSizeClass) var verticalSizeClass - - @State var viewModel: CustomDaxDialogViewModel - - var body: some View { - ZStack { - overlay - - VStack(alignment: .leading, spacing: .zero) { - Spacer() - - daxAndBubbleArrow - - bubbleBody - } - .padding([.leading, .trailing], Constants.Padding.dialogHorizontal) - .if(verticalSizeClass == .regular) { view in - view.padding(.bottom, Constants.Padding.dialogBottom) - } - .if(verticalSizeClass == .compact) { view in - view.padding([.leading, .trailing], Constants.Padding.dialogHorizontalWide) - } - .if(horizontalSizeClass == .regular) { view in - view.frame(width: Constants.Size.fixedDialogWidth) - } - } - } - - @ViewBuilder - private var overlay: some View { - Rectangle() - .foregroundColor(Constants.Colors.overlay) - } - - @ViewBuilder - private var daxAndBubbleArrow: some View { - VStack(spacing: Constants.Spacing.daxLogoAndArrow) { - Image.daxLogo - .resizable() - .frame(width: Constants.Size.daxLogo.width, height: Constants.Size.daxLogo.height) - Triangle() - .frame(width: Constants.Size.bubbleArrow.width, height: Constants.Size.bubbleArrow.height) - .foregroundColor(Constants.Colors.bubbleBackground) - } - .padding(.leading, Constants.Padding.daxLogoAndArrow) - } - - @ViewBuilder - private var bubbleBody: some View { - VStack { - ScrollView { - VStack(spacing: Constants.Spacing.bubbleElements) { - contentElements - - buttons - } - } - .if(verticalSizeClass != .compact) { view in - view.simultaneousGesture(DragGesture(minimumDistance: 0)) - } - .fixedSize(horizontal: false, vertical: true) - } - .padding(Constants.Padding.dialogInsets) - .background( - RoundedRectangle(cornerRadius: Constants.Size.dialogCornerRadius) - .foregroundColor(Constants.Colors.bubbleBackground) - ) - } - - @ViewBuilder - private var contentElements: some View { - ForEach(viewModel.content, id: \.self) { element in - switch element { - case .text(let text): - Text(text) - .font(Constants.Fonts.text) - .foregroundColor(Constants.Colors.text) - .lineSpacing(Constants.Spacing.textLineSpacing) - .frame(maxWidth: .infinity, alignment: .leading) - case .animation(let name, let delay): - LottieView(lottieFile: name, delay: delay) - .fixedSize() - } - } - } - - @ViewBuilder - private var buttons: some View { - ForEach(viewModel.buttons, id: \.self) { button in - switch button { - case .bordered(let label, let action): - Button(action: action, label: { - Text(label) - .font(Constants.Fonts.button) - .frame(maxWidth: .infinity, maxHeight: .infinity) - }) - .frame(height: Constants.Size.buttonHeight) - .foregroundColor(Constants.Colors.borderedButtonText) - .background(Capsule().foregroundColor(Constants.Colors.borderedButtonBackground)) - case .borderless(let label, let action): - Button(action: action, label: { - Text(label) - .font(Constants.Fonts.button) - .frame(maxHeight: .infinity) - }) - .frame(height: Constants.Size.buttonHeight) - .buttonStyle(.borderless) - .foregroundColor(Constants.Colors.borderlessButtonText) - .clipShape(Capsule()) - } - } - } - -} - -private enum Constants { - - enum Fonts { - static let text = Font(UIFont.appFont(ofSize: 17)) - static let button = Font(UIFont.boldAppFont(ofSize: 16)) - } - - enum Colors { - static let overlay = Color("CustomDaxDialogOverlayColor") - static let bubbleBackground = Color("CustomDaxDialogBubbleBackgroundColor") - static let text = Color("CustomDaxDialogTextColor") - static let borderedButtonText = Color("CustomDaxDialogBorderedButtonTextColor") - static let borderlessButtonText = Color("CustomDaxDialogBorderlessButtonTextColor") - static let borderedButtonBackground = Color("CustomDaxDialogBorderedButtonBackgroundColor") - } - - enum Spacing { - static let daxLogoAndArrow: CGFloat = 8 - static let bubbleElements: CGFloat = 16 - static let textLineSpacing: CGFloat = 5 - } - - enum Padding { - static let daxLogoAndArrow: CGFloat = 24 - static let dialogInsets = EdgeInsets(top: 24, leading: 16, bottom: 24, trailing: 16) - static let dialogHorizontal: CGFloat = 8 - static let dialogHorizontalWide: CGFloat = 70 - static let dialogBottom: CGFloat = 92 - } - - enum Size { - static let daxLogo = CGSize(width: 54, height: 54) - static let bubbleArrow = CGSize(width: 15, height: 7) - static let buttonHeight: CGFloat = 44 - static let fixedDialogWidth: CGFloat = 380 - static let dialogCornerRadius: CGFloat = 16 - } -} - -private extension Image { - static let daxLogo = Image("Logo") -} diff --git a/DuckDuckGo/CustomDaxDialogViewModel.swift b/DuckDuckGo/CustomDaxDialogViewModel.swift deleted file mode 100644 index 9a534b6de4..0000000000 --- a/DuckDuckGo/CustomDaxDialogViewModel.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// CustomDaxDialogViewModel.swift -// DuckDuckGo -// -// Copyright © 2023 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 CustomDaxDialogViewModel { - var content: [DialogContentItem] { get } - var buttons: [DialogButtonItem] { get } -} - -enum DialogContentItem: Hashable { - case text(text: String) - case animation(name: String, delay: TimeInterval = 0) -} - -enum DialogButtonItem: Hashable { - case bordered(label: String, action: () -> Void) - case borderless(label: String, action: () -> Void) - - func hash(into hasher: inout Hasher) { - switch self { - case .bordered(let label, _), .borderless(let label, _): - hasher.combine(label) - } - } - - static func == (lhs: DialogButtonItem, rhs: DialogButtonItem) -> Bool { - switch (lhs, rhs) { - case (.bordered(let lhsLabel, _), .bordered(let rhsLabel, _)), - (.borderless(let lhsLabel, _), .borderless(let rhsLabel, _)): - return lhsLabel == rhsLabel - default: - return false - } - } -} diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 271987e51b..485c269970 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -110,8 +110,7 @@ class RootDebugViewController: UITableViewController { override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { if tableView.cellForRow(at: indexPath)?.tag == Row.resetAutoconsentPrompt.rawValue { - AppUserDefaults().autoconsentPromptSeen = false - AppUserDefaults().autoconsentEnabled = false + AppUserDefaults().clearAutoconsentUserSetting() tableView.deselectRow(at: indexPath, animated: true) } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 0e58ca1c50..21e2e3925b 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -2220,38 +2220,6 @@ extension TabViewController: AutoconsentUserScriptDelegate { func autoconsentUserScript(_ script: AutoconsentUserScript, didUpdateCookieConsentStatus cookieConsentStatus: PrivacyDashboard.CookieConsentInfo) { privacyInfo?.cookieConsentManaged = cookieConsentStatus } - - // Disabled temporarily as a result of https://app.asana.com/0/1203936086921904/1204496002772588/f - private var cookieConsentDaxDialogPresentationAllowed: Bool { false } - - func autoconsentUserScript(_ script: AutoconsentUserScript, didRequestAskingUserForConsent completion: @escaping (Bool) -> Void) { - guard cookieConsentDaxDialogPresentationAllowed, - Locale.current.isRegionInEurope, - !isShowingFullScreenDaxDialog else { return } - - let viewModel = CookieConsentDaxDialogViewModel(okAction: { - completion(true) - Pixel.fire(pixel: .daxDialogsAutoconsentConfirmed) - self.dismiss(animated: true) - }, noAction: { - completion(false) - Pixel.fire(pixel: .daxDialogsAutoconsentCancelled) - self.dismiss(animated: true) - }) - - Pixel.fire(pixel: .daxDialogsAutoconsentShown) - - showCustomDaxDialog(viewModel: viewModel) - } - - private func showCustomDaxDialog(viewModel: CustomDaxDialogViewModel) { - let daxDialog = UIHostingController(rootView: CustomDaxDialog(viewModel: viewModel), ignoreSafeArea: true) - daxDialog.modalPresentationStyle = .overFullScreen - daxDialog.modalTransitionStyle = .crossDissolve - daxDialog.view.backgroundColor = .clear - - present(daxDialog, animated: true) - } } // MARK: - AdClickAttributionLogicDelegate diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 531900ac59..8d36552646 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -859,14 +859,6 @@ But if you *do* want a peek under the hood, you can find more information about public static let omnibarNotificationCookiesManaged = NSLocalizedString("omnibar.notification.cookies-managed", value:"Cookies Managed", comment: "Text displayed on notification appearing in the address bar when the browser dismissed the cookie popup automatically rejecting it") public static let omnibarNotificationPopupHidden = NSLocalizedString("omnibar.notification.popup-hidden", value:"Pop-up Hidden", comment: "Text displayed on notification appearing in the address bar when the browser hides a cookie popup") - // MARK: Dax Dialog - - public static let daxDialogCookieConsentFirst = NSLocalizedString("dax.cookie-consent.first", value:"Looks like this site has a cookie consent pop-up👇", comment: "First part of text displayed on Dax dialog for enabling Autoconsent for Cookie Management feature") - public static let daxDialogCookieConsentSecond = NSLocalizedString("dax.cookie-consent.second", value:"Want me to handle these for you? I can try to minimize cookies, maximize privacy, and hide pop-ups like these.", comment: "Second part of text displayed on Dax dialog for enabling Autoconsent for Cookie Management feature") - - public static let daxDialogCookieConsentAcceptButton = NSLocalizedString("dax.cookie-consent.button.accept", value:"Manage Cookie Pop-ups", comment: "Button title accepting to enable feature to automatically manage cookie popups") - public static let daxDialogCookieConsentRejectButton = NSLocalizedString("dax.cookie-consent.button.reject", value:"No Thanks", comment: "Button title rejecting to enable feature to automatically manage cookie popups") - // MARK: Sync public static let syncTurnOffConfirmTitle = NSLocalizedString("sync.turn.off.confirm.title", value:"Turn Off Sync?", comment: "Title of the dialog to confirm turning off Sync") diff --git a/DuckDuckGo/cookie-banner-illustration-animated-dark.json b/DuckDuckGo/cookie-banner-illustration-animated-dark.json deleted file mode 100644 index 9db3493072..0000000000 --- a/DuckDuckGo/cookie-banner-illustration-animated-dark.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.9.0","fr":24,"ip":0,"op":12,"w":296,"h":64,"nm":"Cookie Banner Animation (Dark)","ddd":0,"assets":[{"id":"comp_0","nm":"Banner Illustration","fr":24,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Chips","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.862,32.895,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-3.016,-5.384],[-1.452,-6.948],[-3.016,-8.512],[-4.58,-6.948]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-6.948,-1.497],[-5.384,-3.061],[-6.948,-4.625],[-8.513,-3.061]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[1.139,0.648],[2.703,-0.916],[1.139,-2.48],[-0.425,-0.916]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[7.082,4.581],[8.646,3.017],[7.082,1.453],[5.518,3.017]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-0.782,8.512],[0.782,6.948],[-0.782,5.384],[-2.346,6.948]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-7.082,4.536],[-5.518,2.972],[-7.082,1.408],[-8.646,2.972]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.352941185236,0.156862750649,0.027450980619,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Chips","np":8,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Cookie","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.442,32.374,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.971,0],[-0.116,-0.525],[0,-1.081],[8.008,0],[0,8.008],[-8.008,0],[-1.013,-0.224],[0,-0.538]],"o":[[0.538,0],[0.224,1.013],[0,8.008],[-8.008,0],[0,-8.008],[1.081,0],[0.525,0.116],[0,4.971]],"v":[[13,-4],[14.158,-3.147],[14.5,0],[0,14.5],[-14.5,0],[0,-14.5],[3.147,-14.158],[4,-13]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.758499979973,0.502933323383,0.061500012875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.745098054409,0.101960785687,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Cookie","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Faux Text","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[141.481,18.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-1.105],[-1.105,0],[0,0],[0,1.105],[1.105,0]],"o":[[-1.105,0],[0,1.105],[0,0],[1.105,0],[0,-1.105],[0,0]],"v":[[-53.5,-6.5],[-55.5,-4.5],[-53.5,-2.5],[-39.5,-2.5],[-37.5,-4.5],[-39.5,-6.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,-1.105],[-1.105,0],[0,0],[0,1.105],[1.105,0]],"o":[[-1.105,0],[0,1.105],[0,0],[1.105,0],[0,-1.105],[0,0]],"v":[[-31.5,-6.5],[-33.5,-4.5],[-31.5,-2.5],[-10.5,-2.5],[-8.5,-4.5],[-10.5,-6.5]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0],[0,0]],"o":[[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0],[-1.105,0]],"v":[[3.5,-4.5],[5.5,-6.5],[53.5,-6.5],[55.5,-4.5],[53.5,-2.5],[5.5,-2.5]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[1.105,0],[0,-1.105],[-1.105,0],[0,1.105]],"o":[[-1.105,0],[0,1.105],[1.105,0],[0,-1.105]],"v":[[-2.5,-6.5],[-4.5,-4.5],[-2.5,-2.5],[-0.5,-4.5]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0],[0,0]],"o":[[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0],[-1.105,0]],"v":[[-9.5,4.5],[-7.5,2.5],[10.5,2.5],[12.5,4.5],[10.5,6.5],[-7.5,6.5]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[0,0],[0,-1.105],[-1.105,0],[0,0],[0,1.105],[1.105,0]],"o":[[-1.105,0],[0,1.105],[0,0],[1.105,0],[0,-1.105],[0,0]],"v":[[-53.5,2.5],[-55.5,4.5],[-53.5,6.5],[-13.5,6.5],[-11.5,4.5],[-13.5,2.5]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ind":6,"ty":"sh","ix":7,"ks":{"a":0,"k":{"i":[[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0],[0,0]],"o":[[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0],[-1.105,0]],"v":[[15.5,4.5],[17.5,2.5],[35.5,2.5],[37.5,4.5],[35.5,6.5],[17.5,6.5]],"c":true},"ix":2},"nm":"Path 7","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.033598780632,0.114138200879,0.332745105028,1],"ix":4},"o":{"a":0,"k":10,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Faux Text","np":9,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Button 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114.981,42.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[58,19],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":4,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.033598780632,0.114138200879,0.332745105028,1],"ix":4},"o":{"a":0,"k":10,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Button","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Button 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[180.981,42.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[58,19],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":4,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.033598780632,0.114138200879,0.332745105028,1],"ix":4},"o":{"a":0,"k":30,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Button","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Background Shape","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[124,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[246,62],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.800000011921,0.843137264252,0.972549021244,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039221764,0.92549020052,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Marks - Right","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":9,"s":[50]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[274,32,0],"to":[2.333,0,0],"ti":[-2.333,0,0]},{"t":9,"s":[288,32,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":5,"s":[-50,50,100]},{"t":9,"s":[-100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.502,-0.984],[-0.984,-0.502],[0,0],[-0.502,0.984],[0.984,0.502],[0,0]],"o":[[-0.502,0.984],[0,0],[0.984,0.502],[0.502,-0.984],[0,0],[-0.984,-0.502]],"v":[[-3.28,-21.036],[-2.408,-18.346],[4.717,-14.708],[7.407,-15.58],[6.536,-18.27],[-0.589,-21.908]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.717,-0.841],[-0.841,0.717],[0,0],[0.717,0.841],[0.841,-0.717],[0,0]],"o":[[0.717,0.841],[0,0],[0.841,-0.717],[-0.717,-0.841],[0,0],[-0.841,0.717]],"v":[[-1.726,21.425],[1.094,21.649],[7.181,16.458],[7.405,13.639],[4.586,13.415],[-1.502,18.605]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0]],"o":[[-1.105,0],[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0]],"v":[[-5.883,1.16],[-7.883,-0.84],[-5.883,-2.84],[2.117,-2.84],[4.117,-0.84],[2.117,1.16]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.329411764706,0.329411764706,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Marks","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Marks - Left","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":9,"s":[50]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[20,32,0],"to":[-2,0,0],"ti":[2,0,0]},{"t":9,"s":[8,32,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":5,"s":[50,50,100]},{"t":9,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.502,-0.984],[-0.984,-0.502],[0,0],[-0.502,0.984],[0.984,0.502],[0,0]],"o":[[-0.502,0.984],[0,0],[0.984,0.502],[0.502,-0.984],[0,0],[-0.984,-0.502]],"v":[[-3.28,-21.036],[-2.408,-18.346],[4.717,-14.708],[7.407,-15.58],[6.536,-18.27],[-0.589,-21.908]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.717,-0.841],[-0.841,0.717],[0,0],[0.717,0.841],[0.841,-0.717],[0,0]],"o":[[0.717,0.841],[0,0],[0.841,-0.717],[-0.717,-0.841],[0,0],[-0.841,0.717]],"v":[[-1.726,21.425],[1.094,21.649],[7.181,16.458],[7.405,13.639],[4.586,13.415],[-1.502,18.605]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0]],"o":[[-1.105,0],[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0]],"v":[[-5.883,1.16],[-7.883,-0.84],[-5.883,-2.84],[2.117,-2.84],[4.117,-0.84],[2.117,1.16]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,0.329411764706,0.329411764706,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Marks","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Banner Illustration","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":6,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[148,32,0],"ix":2,"l":2},"a":{"a":0,"k":[124,32,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[50,50,100]},{"t":6,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"w":248,"h":64,"ip":0,"op":24,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/DuckDuckGo/cookie-banner-illustration-animated.json b/DuckDuckGo/cookie-banner-illustration-animated.json deleted file mode 100644 index 3da336c3c6..0000000000 --- a/DuckDuckGo/cookie-banner-illustration-animated.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"5.9.0","fr":24,"ip":0,"op":12,"w":296,"h":64,"nm":"Cookie Banner Animation","ddd":0,"assets":[{"id":"comp_0","nm":"Banner Illustration","fr":24,"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Chips","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.862,32.895,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-3.016,-5.384],[-1.452,-6.948],[-3.016,-8.512],[-4.58,-6.948]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-6.948,-1.497],[-5.384,-3.061],[-6.948,-4.625],[-8.513,-3.061]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[1.139,0.648],[2.703,-0.916],[1.139,-2.48],[-0.425,-0.916]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[7.082,4.581],[8.646,3.017],[7.082,1.453],[5.518,3.017]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-0.782,8.512],[0.782,6.948],[-0.782,5.384],[-2.346,6.948]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[-0.864,0],[0,0.864],[0.864,0],[0,-0.864]],"o":[[0.864,0],[0,-0.864],[-0.864,0],[0,0.864]],"v":[[-7.082,4.536],[-5.518,2.972],[-7.082,1.408],[-8.646,2.972]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.352941185236,0.156862750649,0.027450980619,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Chips","np":8,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Cookie","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.442,32.374,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.971,0],[-0.116,-0.525],[0,-1.081],[8.008,0],[0,8.008],[-8.008,0],[-1.013,-0.224],[0,-0.538]],"o":[[0.538,0],[0.224,1.013],[0,8.008],[-8.008,0],[0,-8.008],[1.081,0],[0.525,0.116],[0,4.971]],"v":[[13,-4],[14.158,-3.147],[14.5,0],[0,14.5],[-14.5,0],[0,-14.5],[3.147,-14.158],[4,-13]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.758499979973,0.502933323383,0.061500012875,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.976470589638,0.745098054409,0.101960785687,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Cookie","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Faux Text","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[141.481,18.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-1.105],[-1.105,0],[0,0],[0,1.105],[1.105,0]],"o":[[-1.105,0],[0,1.105],[0,0],[1.105,0],[0,-1.105],[0,0]],"v":[[-53.5,-6.5],[-55.5,-4.5],[-53.5,-2.5],[-39.5,-2.5],[-37.5,-4.5],[-39.5,-6.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[0,-1.105],[-1.105,0],[0,0],[0,1.105],[1.105,0]],"o":[[-1.105,0],[0,1.105],[0,0],[1.105,0],[0,-1.105],[0,0]],"v":[[-31.5,-6.5],[-33.5,-4.5],[-31.5,-2.5],[-10.5,-2.5],[-8.5,-4.5],[-10.5,-6.5]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0],[0,0]],"o":[[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0],[-1.105,0]],"v":[[3.5,-4.5],[5.5,-6.5],[53.5,-6.5],[55.5,-4.5],[53.5,-2.5],[5.5,-2.5]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ind":3,"ty":"sh","ix":4,"ks":{"a":0,"k":{"i":[[1.105,0],[0,-1.105],[-1.105,0],[0,1.105]],"o":[[-1.105,0],[0,1.105],[1.105,0],[0,-1.105]],"v":[[-2.5,-6.5],[-4.5,-4.5],[-2.5,-2.5],[-0.5,-4.5]],"c":true},"ix":2},"nm":"Path 4","mn":"ADBE Vector Shape - Group","hd":false},{"ind":4,"ty":"sh","ix":5,"ks":{"a":0,"k":{"i":[[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0],[0,0]],"o":[[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0],[-1.105,0]],"v":[[-9.5,4.5],[-7.5,2.5],[10.5,2.5],[12.5,4.5],[10.5,6.5],[-7.5,6.5]],"c":true},"ix":2},"nm":"Path 5","mn":"ADBE Vector Shape - Group","hd":false},{"ind":5,"ty":"sh","ix":6,"ks":{"a":0,"k":{"i":[[0,0],[0,-1.105],[-1.105,0],[0,0],[0,1.105],[1.105,0]],"o":[[-1.105,0],[0,1.105],[0,0],[1.105,0],[0,-1.105],[0,0]],"v":[[-53.5,2.5],[-55.5,4.5],[-53.5,6.5],[-13.5,6.5],[-11.5,4.5],[-13.5,2.5]],"c":true},"ix":2},"nm":"Path 6","mn":"ADBE Vector Shape - Group","hd":false},{"ind":6,"ty":"sh","ix":7,"ks":{"a":0,"k":{"i":[[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0],[0,0]],"o":[[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0],[-1.105,0]],"v":[[15.5,4.5],[17.5,2.5],[35.5,2.5],[37.5,4.5],[35.5,6.5],[17.5,6.5]],"c":true},"ix":2},"nm":"Path 7","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.033598780632,0.114138200879,0.332745105028,1],"ix":4},"o":{"a":0,"k":10,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Faux Text","np":9,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Button 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[114.981,42.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[58,19],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":4,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.033598780632,0.114138200879,0.332745105028,1],"ix":4},"o":{"a":0,"k":10,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Button","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Button 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[180.981,42.5,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[58,19],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":4,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0.033598780632,0.114138200879,0.332745105028,1],"ix":4},"o":{"a":0,"k":30,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Button","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Background Shape","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[124,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[246,62],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.800000011921,0.843137264252,0.972549021244,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.898039221764,0.92549020052,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":24,"st":0,"bm":0}]}],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Marks - Right","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":9,"s":[30]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[274,32,0],"to":[2.333,0,0],"ti":[-2.333,0,0]},{"t":9,"s":[288,32,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":5,"s":[-50,50,100]},{"t":9,"s":[-100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.502,-0.984],[-0.984,-0.502],[0,0],[-0.502,0.984],[0.984,0.502],[0,0]],"o":[[-0.502,0.984],[0,0],[0.984,0.502],[0.502,-0.984],[0,0],[-0.984,-0.502]],"v":[[-3.28,-21.036],[-2.408,-18.346],[4.717,-14.708],[7.407,-15.58],[6.536,-18.27],[-0.589,-21.908]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.717,-0.841],[-0.841,0.717],[0,0],[0.717,0.841],[0.841,-0.717],[0,0]],"o":[[0.717,0.841],[0,0],[0.841,-0.717],[-0.717,-0.841],[0,0],[-0.841,0.717]],"v":[[-1.726,21.425],[1.094,21.649],[7.181,16.458],[7.405,13.639],[4.586,13.415],[-1.502,18.605]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0]],"o":[[-1.105,0],[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0]],"v":[[-5.883,1.16],[-7.883,-0.84],[-5.883,-2.84],[2.117,-2.84],[4.117,-0.84],[2.117,1.16]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333337307,0.06274510175,0.145098045468,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Marks","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Marks - Left","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":5,"s":[0]},{"t":9,"s":[30]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[20,32,0],"to":[-2,0,0],"ti":[2,0,0]},{"t":9,"s":[8,32,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":5,"s":[50,50,100]},{"t":9,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.502,-0.984],[-0.984,-0.502],[0,0],[-0.502,0.984],[0.984,0.502],[0,0]],"o":[[-0.502,0.984],[0,0],[0.984,0.502],[0.502,-0.984],[0,0],[-0.984,-0.502]],"v":[[-3.28,-21.036],[-2.408,-18.346],[4.717,-14.708],[7.407,-15.58],[6.536,-18.27],[-0.589,-21.908]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-0.717,-0.841],[-0.841,0.717],[0,0],[0.717,0.841],[0.841,-0.717],[0,0]],"o":[[0.717,0.841],[0,0],[0.841,-0.717],[-0.717,-0.841],[0,0],[-0.841,0.717]],"v":[[-1.726,21.425],[1.094,21.649],[7.181,16.458],[7.405,13.639],[4.586,13.415],[-1.502,18.605]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[0,0],[0,1.105],[-1.105,0],[0,0],[0,-1.105],[1.105,0]],"o":[[-1.105,0],[0,-1.105],[0,0],[1.105,0],[0,1.105],[0,0]],"v":[[-5.883,1.16],[-7.883,-0.84],[-5.883,-2.84],[2.117,-2.84],[4.117,-0.84],[2.117,1.16]],"c":true},"ix":2},"nm":"Path 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933333337307,0.06274510175,0.145098045468,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Marks","np":5,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":0,"nm":"Banner Illustration","refId":"comp_0","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":6,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[148,32,0],"ix":2,"l":2},"a":{"a":0,"k":[124,32,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":0,"s":[50,50,100]},{"t":6,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"w":248,"h":64,"ip":0,"op":24,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 815afd61b6..08268ce2dc 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -811,18 +811,6 @@ /* Title for a section containing only items from yesterday */ "date.range.yesterday" = "Yesterday"; -/* Button title accepting to enable feature to automatically manage cookie popups */ -"dax.cookie-consent.button.accept" = "Manage Cookie Pop-ups"; - -/* Button title rejecting to enable feature to automatically manage cookie popups */ -"dax.cookie-consent.button.reject" = "No Thanks"; - -/* First part of text displayed on Dax dialog for enabling Autoconsent for Cookie Management feature */ -"dax.cookie-consent.first" = "Looks like this site has a cookie consent pop-up👇"; - -/* Second part of text displayed on Dax dialog for enabling Autoconsent for Cookie Management feature */ -"dax.cookie-consent.second" = "Want me to handle these for you? I can try to minimize cookies, maximize privacy, and hide pop-ups like these."; - /* No comment provided by engineer. */ "dax.hide.button" = "Hide Tips Forever"; diff --git a/DuckDuckGoTests/AppSettingsMock.swift b/DuckDuckGoTests/AppSettingsMock.swift index 1902cfb9fe..6158e60eff 100644 --- a/DuckDuckGoTests/AppSettingsMock.swift +++ b/DuckDuckGoTests/AppSettingsMock.swift @@ -69,6 +69,5 @@ class AppSettingsMock: AppSettings { widgetInstalled } - var autoconsentPromptSeen = true var autoconsentEnabled = true } diff --git a/DuckDuckGoTests/AppUserDefaultsTests.swift b/DuckDuckGoTests/AppUserDefaultsTests.swift index 89d6e4703f..8556b0643d 100644 --- a/DuckDuckGoTests/AppUserDefaultsTests.swift +++ b/DuckDuckGoTests/AppUserDefaultsTests.swift @@ -18,21 +18,30 @@ // import XCTest -@testable import DuckDuckGo import BrowserServicesKit +@testable import DuckDuckGo +@testable import Core + class AppUserDefaultsTests: XCTestCase { let testGroupName = "test" var internalUserDeciderStore: MockInternalUserStoring! + var customSuite: UserDefaults! override func setUp() { super.setUp() - UserDefaults(suiteName: testGroupName)?.removePersistentDomain(forName: testGroupName) + customSuite = UserDefaults(suiteName: testGroupName) + customSuite.removePersistentDomain(forName: testGroupName) internalUserDeciderStore = MockInternalUserStoring() + + // Isolate defaults for UserDefaultsWrapper + UserDefaults.app = customSuite } override func tearDown() { + UserDefaults.app = .standard + internalUserDeciderStore = nil super.tearDown() } @@ -157,6 +166,33 @@ class AppUserDefaultsTests: XCTestCase { XCTAssertEqual(appUserDefaults.autofillCredentialsEnabled, false) } + func testDefaultAutoconsentStateIsFalse_WhenNotInRollout() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: false) + XCTAssertFalse(appUserDefaults.autoconsentEnabled) + } + + func testDefaultAutoconsentStateIsTrue_WhenInRollout() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: true) + XCTAssertTrue(appUserDefaults.autoconsentEnabled) + } + + func testAutoconsentReadsUserStoredValue_RegardlessOfRolloutState() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + + // When setting disabled by user and rollout enabled + appUserDefaults.autoconsentEnabled = false + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: true) + + XCTAssertFalse(appUserDefaults.autoconsentEnabled) + + // When setting enabled by user and rollout disabled + appUserDefaults.autoconsentEnabled = true + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: false) + + XCTAssertTrue(appUserDefaults.autoconsentEnabled) + } // MARK: - Mock Creation diff --git a/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift b/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift index aae48f81e3..3b0ce23cce 100644 --- a/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift +++ b/DuckDuckGoTests/AutoconsentMessageProtocolTests.swift @@ -184,6 +184,5 @@ class MockWKScriptMessage: WKScriptMessage { } class MockAutoconsentPreferences: AutoconsentPreferences { - var autoconsentPromptSeen: Bool = true var autoconsentEnabled: Bool = true } diff --git a/IntegrationTests/AutoconsentBackgroundTests.swift b/IntegrationTests/AutoconsentBackgroundTests.swift index f4e309eca9..5b7cc496d4 100644 --- a/IntegrationTests/AutoconsentBackgroundTests.swift +++ b/IntegrationTests/AutoconsentBackgroundTests.swift @@ -158,6 +158,5 @@ class MockEmbeddedDataProvider: EmbeddedDataProvider { } class MockAutoconsentPreferences: AutoconsentPreferences { - var autoconsentPromptSeen: Bool = true var autoconsentEnabled: Bool = true } From e06582a88314448155630cb28fe91d03075f051c Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 5 Feb 2024 10:39:39 +0100 Subject: [PATCH 002/245] Autofill: Fix footer sizing for multiline labels (#2422) Task/Issue URL: https://app.asana.com/0/1201462886803403/1206371260426517/f Tech Design URL: CC: Description: This is a fix for the autofill tableview footer label not sizing correctly when there is more than one line of text --- DuckDuckGo/AutofillSettingsEnableFooterView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/AutofillSettingsEnableFooterView.swift b/DuckDuckGo/AutofillSettingsEnableFooterView.swift index 488a53ac4b..1785e0a6d7 100644 --- a/DuckDuckGo/AutofillSettingsEnableFooterView.swift +++ b/DuckDuckGo/AutofillSettingsEnableFooterView.swift @@ -54,10 +54,14 @@ class AutofillSettingsEnableFooterView: UIView { private func installConstraints() { title.translatesAutoresizingMaskIntoConstraints = false + + let bottomConstraint = title.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -Constants.defaultPadding) + // setting priority to ensure multiline text is displayed correctly + bottomConstraint.priority = .defaultHigh NSLayoutConstraint.activate([ title.topAnchor.constraint(equalTo: self.topAnchor, constant: Constants.topPadding), - title.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: -Constants.defaultPadding), + bottomConstraint, title.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: Constants.defaultPadding), title.trailingAnchor.constraint(lessThanOrEqualTo: self.trailingAnchor, constant: -Constants.defaultPadding) ]) From 8ddde537a2e0637aeb23ad1f9ca165c2923b190c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 5 Feb 2024 11:47:44 +0100 Subject: [PATCH 003/245] Release 7.108.0-0 (#2432) --- Configuration/Version.xcconfig | 2 +- Core/AppPrivacyConfigurationDataProvider.swift | 4 ++-- Core/ios-config.json | 2 +- DuckDuckGo/Settings.bundle/Root.plist | 2 +- fastlane/metadata/default/release_notes.txt | 1 + 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index d31068acdf..6c153b1550 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.107.0 +MARKETING_VERSION = 7.108.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index e9277bd629..aad4388e47 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"d0ae514c42e1e632584aba7a025b8b92\"" - public static let embeddedDataSHA = "b304a2dbb2edc7443a4950bb2ba9f7604354cf32575dd5a9ca09acd5c4b78146" + public static let embeddedDataETag = "\"4796cc720aa6849f3a8d27610ab20aac\"" + public static let embeddedDataSHA = "1fb091f103d9b382cd1bc1bc1577591e63c8e11f7b2c3b52c2f5392b992295e2" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index 311a1e6e36..10c4861e9d 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1706638025243, + "version": 1706872224316, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index bcd44a47ed..c7c1d02b51 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.107.0 + 7.108.0 Key version Title diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index fe8f05ed3b..6cd7a9e939 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,2 +1,3 @@ - Bug fixes and other improvements. + Join our fully distributed team and help raise the standard of trust online! https://duckduckgo.com/hiring From 5bf79e800ea5f646d6a200349954e49da2005e81 Mon Sep 17 00:00:00 2001 From: Maxim Tsoy Date: Mon, 5 Feb 2024 16:34:04 +0100 Subject: [PATCH 004/245] Autoconsent 9.7.2 (#2425) --- DuckDuckGo/Autoconsent/autoconsent-bundle.js | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index a61c1559d1..7775dafeb4 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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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_AXEPTIO_0:()=>document.cookie.includes("axeptio_authorized_vendors=%2C%2C"),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_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_COMPLIANZ_CATEGORIES_0:()=>!!document.cookie.match(/cmplz_[^=]+=deny/),EVAL_COMPLIANZ_OPTIN_0:()=>!!document.cookie.match(/cookieconsent_preferences_disabled=[^;]+/),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_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_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(/ezCMPCookieConsent=[^;]+\|2=0\|3=0\|4=0/),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_0:()=>document.cookie.includes("euconsent-v2"),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_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_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:()=>"_cookies_accepted=essential"===document.cookie,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_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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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))}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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}var g="#truste-show-consent",y="#truste-consent-track",w=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${y}`],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},${y}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${y}`,"all")}openFrame(){this.click(g)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${y}`),this.click(g),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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(1e3),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 d{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"),!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 d{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")||(k(h(),"#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 d{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")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk","all")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-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,.js-accept-cookies")}async test(){return await b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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")}async detectPopup(){return this.elementVisible(".unic .unic-box,.unic .unic-bar","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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],C=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var v=[{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:"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:"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:"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:"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:"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},{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:[{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:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{click:".cc-dismiss"}],test:[{eval:"EVAL_COMPLIANZ_CATEGORIES_0"}]},{name:"Complianz notice",prehideSelectors:['.cc-type-info[aria-describedby="cookieconsent:desc"]'],cosmetic:!0,detectCmp:[{exists:'.cc-type-info[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-info[aria-describedby="cookieconsent:desc"]'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{hide:'[aria-describedby="cookieconsent:desc"]'}]},{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:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{click:".cc-settings"},{waitForVisible:'[aria-label="cookies preferences popup"]'},{click:'[aria-label="cookies preferences popup"] input[type=checkbox]:not([disabled]):checked',all:!0,optional:!0},{click:'[aria-label="cookies preferences popup"] [aria-label="Accept Selected"], [aria-label="cookies preferences popup"] [aria-label="Save my choice"], .cc-btn-accept-selected, .cc-deny',optional:!0}],test:[{eval:"EVAL_COMPLIANZ_OPTIN_0"}]},{name:"cookie-law-info",prehideSelectors:["#cookie-law-info-bar"],detectCmp:[{exists:"#cookie-law-info-bar"}],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:"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:"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:"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:"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:".eu-cookie-compliance-banner-info"}],detectPopup:[{exists:".eu-cookie-compliance-popup-open"}],optIn:[{click:".agree-button"}],optOut:[{click:".decline-button,.eu-cookie-compliance-save-preferences-button",optional:!0},{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:"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:"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:"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:"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:"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:"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:"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",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:[{click:"#lanyard_root button[class^='link']",optional:!0},{if:{exists:"#lanyard_root button[class*='confirmButton']"},then:[{waitForThenClick:"#lanyard_root button[class*='rejectButton']"},{click:"#lanyard_root button[class*='confirmButton']"}],else:[{click:"#lanyard_root div[class^='buttons'] > :nth-child(1)",optional:!0},{waitForThenClick:"#lanyard_root input:checked"},{click:"#consentsTab > div:nth-child(2) > div > div[class^='actions'] > button:nth-child(1)"}]}],test:[]},{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:"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:"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:"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:"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"],cosmetic:!0,detectCmp:[{exists:".osano-cm-window"}],detectPopup:[{visible:".osano-cm-dialog"}],optIn:[{click:".osano-cm-accept-all",optional:!0}],optOut:[{hide:".osano-cm-window"}]},{name:"otto.de",prehideSelectors:[".cookieBanner--visibility"],detectCmp:[{exists:".cookieBanner--visibility"}],detectPopup:[{visible:".cookieBanner__wrapper"}],optIn:[{click:".js_cookieBannerPermissionButton"}],optOut:[{click:".js_cookieBannerProhibitionButton"}]},{name:"paypal-us",prehideSelectors:["#ccpaCookieContent_wrapper, article.ppvx_modal--overpanel"],detectCmp:[{exists:"#ccpaCookieBanner, .privacy-modal-content"}],detectPopup:[{exists:"#ccpaCookieBanner, .privacy-modal-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"a#manageCookiesLink"},then:[{click:"a#manageCookiesLink"}],else:[{waitForVisible:".privacy-modal-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:"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:['section:has(a[href^="https://www.reddit.com/policies/cookies"])'],detectCmp:[{exists:'section:has(a[href^="https://www.reddit.com/policies/cookies"])'}],detectPopup:[{visible:'section:has(a[href^="https://www.reddit.com/policies/cookies"])'}],optIn:[{waitForThenClick:"section:has(a[href^=\"https://www.reddit.com/policies/cookies\"]) section[class^='_'] > section:first-child form button"}],optOut:[{waitForThenClick:"section:has(a[href^=\"https://www.reddit.com/policies/cookies\"]) section[class^='_'] > section:last-child form button"}],test:[{eval:"EVAL_REDDIT_0"}]},{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:"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",prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:"#sd-cmp .sd-cmp-1pO44"}],test:[{eval:"EVAL_SIRDATA_0"}]},{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:"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:"Tealium",prehideSelectors:["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#consent-layer"],detectCmp:[{exists:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *"},{eval:"EVAL_TEALIUM_0"}],detectPopup:[{visible:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *",check:"any"}],optOut:[{eval:"EVAL_TEALIUM_1"},{eval:"EVAL_TEALIUM_DONOTSELL"},{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs"},{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:"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:"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:"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.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.p-button"},{waitForThenClick:'xpath///*[@id="cookie-policy-content"]/p[4]/button[2]'}]},{waitForThenClick:".p-switch__input:checked",optional:!0,all:!0},{any:[{waitForThenClick:"div > button"},{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:"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:"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:"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:"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:[]}],f={"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"}]}},A={autoconsent:v,consentomatic:f},E=Object.freeze({__proto__:null,autoconsent:v,consentomatic:f,default:A});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 C(this)}initialize(e,t){const o=function(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=structuredClone(t);for(const c of Object.keys(t))void 0!==e[c]&&(o[c]=e[c]);return o}(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(){w.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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"});let o=await this.detectPopups(t.filter((e=>!e.isCosmetic)));if(0===o.length&&(o=await this.detectPopups(t.filter((e=>e.isCosmetic)))),0===o.length)return e.lifecycle&&console.log("no popup found"),this.config.enablePrehide&&this.undoPrehide(),!1;if(this.updateState({lifecycle:"openPopupDetected"}),this.config.enablePrehide&&!this.state.prehideOn&&this.prehideElements(),o.length>1){const t={msg:"Found multiple CMPs, check the detection rules.",cmps:o.map((e=>e.name))};e.errors&&console.warn(t.msg,t.cmps),this.sendContentMessage({type:"autoconsentError",details:t})}return this.foundCmp=o[0],"optOut"===this.config.autoAction?await this.doOptOut():"optIn"===this.config.autoAction?await this.doOptIn():(e.lifecycle&&console.log("waiting for opt-out signal...",location.href),!0)}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 detectPopups(e){const t=this.config.logs,o=[],c=e.map((e=>this.waitForPopup(e).then((t=>{t&&(this.updateState({detectedPopups:this.state.detectedPopups.concat([e.name])}),this.sendContentMessage({type:"popupFound",cmp:e.name,url:location.href}),o.push(e))})).catch((o=>(t.errors&&console.warn(`error waiting for a popup for ${e.name}`,o),null)))));return await Promise.all(c),o}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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,E);window.autoconsentMessageCallback=e=>{O.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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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_0:()=>document.cookie.includes("euconsent-v2"),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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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 g(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 y="#truste-show-consent",w="#truste-consent-track",C=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${w}`],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(`${y},${w}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${w}`,"all")}openFrame(){this.click(y)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${w}`),this.click(y),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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 d{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 d{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")||(k(h(),"#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 d{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")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk","all")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-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,.js-accept-cookies")}async test(){return await b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],v=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var f=[{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},{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:[{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",prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:"#sd-cmp .sd-cmp-1pO44"}],test:[{eval:"EVAL_SIRDATA_0"}]},{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:[]}],A={"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"}]}},E={autoconsent:f,consentomatic:A},x=Object.freeze({__proto__:null,autoconsent:f,consentomatic:A,default:E});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 v(this)}initialize(e,t){const o=g(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(){C.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,x);window.autoconsentMessageCallback=e=>{O.receiveMessageCallback(e)}}(); diff --git a/package-lock.json b/package-lock.json index 6ba27d8bf1..2947f69fc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "ios", "version": "1.0.0", "dependencies": { - "@duckduckgo/autoconsent": "^9.1.0" + "@duckduckgo/autoconsent": "^9.7.2" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", @@ -135,9 +135,9 @@ } }, "node_modules/@duckduckgo/autoconsent": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-9.1.0.tgz", - "integrity": "sha512-zjuYu+Wb/8cscFrjSttA4uMjxwcRAzPcHmou5vgbYAfEV7qdsAPMvuai2REke199BmoI7OK2khtsLq7LGqkDzQ==" + "version": "9.7.2", + "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-9.7.2.tgz", + "integrity": "sha512-LaJdRoZwRneDPzzVQdiHLPNjMW6B773edisKXiWYUXZOIXzGbwifKjCiYHTtDtaeaLjpuiWXlz2a9yEwMszK2A==" }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", diff --git a/package.json b/package.json index d0973a9923..3224a7c33c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ "rollup-plugin-terser": "^7.0.2" }, "dependencies": { - "@duckduckgo/autoconsent": "^9.1.0" + "@duckduckgo/autoconsent": "^9.7.2" } } From f347d5acf5814c498cce1285dc982b6b8ec647d2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 5 Feb 2024 16:48:11 +0100 Subject: [PATCH 005/245] Hide the debug menu when debug mode is not enabled (#2436) --- DuckDuckGo/SettingsDebugView.swift | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo/SettingsDebugView.swift b/DuckDuckGo/SettingsDebugView.swift index 39615314c2..8baacecbf5 100644 --- a/DuckDuckGo/SettingsDebugView.swift +++ b/DuckDuckGo/SettingsDebugView.swift @@ -25,13 +25,15 @@ struct SettingsDebugView: View { @EnvironmentObject var viewModel: SettingsViewModel var body: some View { - Section(header: Text("Debug")) { - - SettingsCellView(label: "All debug options", - action: { viewModel.presentLegacyView(.debug) }, - disclosureIndicator: true, - isButton: true) - + if viewModel.state.debugModeEnabled { + Section(header: Text("Debug")) { + + SettingsCellView(label: "All debug options", + action: { viewModel.presentLegacyView(.debug) }, + disclosureIndicator: true, + isButton: true) + + } } } From 86681277da832f89a837cee8c4b328fad313238c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 5 Feb 2024 16:56:46 +0100 Subject: [PATCH 006/245] Release 7.107.1-0 (#2437) --- Configuration/Version.xcconfig | 2 +- DuckDuckGo/Settings.bundle/Root.plist | 2 +- scripts/prepare_release.sh | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index d31068acdf..fffff143d7 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.107.0 +MARKETING_VERSION = 7.107.1 diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index bcd44a47ed..70371a4ad5 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.107.0 + 7.107.1 Key version Title diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 4a5bde66d3..d413781f84 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -153,6 +153,11 @@ create_build_branch() { local temp_file local latest_build_number + if [[ $is_hotfix ]]; then + version=$(cut -d' ' -f3 < "${base_dir}/Configuration/Version.xcconfig") + version=$(bump_patch_number "$version") + fi + temp_file=$(mktemp) bundle exec fastlane latest_build_number_for_version version:"$version" file_name:"$temp_file" latest_build_number="$(<"$temp_file")" @@ -171,11 +176,6 @@ create_build_branch() { update_marketing_version() { printf '%s' "Setting app version ... " - if [[ $is_hotfix ]]; then - version=$(cut -d' ' -f3 < "${base_dir}/Configuration/Version.xcconfig") - version=$(bump_patch_number "$version") - fi - "$script_dir/set_version.sh" "${version}" git add "${base_dir}/Configuration/Version.xcconfig" \ "${base_dir}/DuckDuckGo/Settings.bundle/Root.plist" From 68b5af1192e5b3c7c9a548bd935c5b70312f5095 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 5 Feb 2024 17:08:36 +0100 Subject: [PATCH 007/245] Updates internal user flag to reset state (#2438) Task/Issue URL: https://app.asana.com/0/547792610048271/1206524375402369/f Description: Prevent debug menu from showing to non-internal users. Reset internal user state for all users. --- Core/UserDefaultsPropertyWrapper.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 850a3a9738..cda77ee3fa 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -83,7 +83,8 @@ public struct UserDefaultsWrapper { case autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = "com.duckduckgo.ios.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary" - case featureFlaggingDidVerifyInternalUser = "com.duckduckgo.app.featureFlaggingDidVerifyInternalUser" + // .v2 suffix added to fix https://app.asana.com/0/547792610048271/1206524375402369/f + case featureFlaggingDidVerifyInternalUser = "com.duckduckgo.app.featureFlaggingDidVerifyInternalUser.v2" case voiceSearchEnabled = "com.duckduckgo.app.voiceSearchEnabled" From 2ca09851c2538f97374b907a2a9227ae526e5718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 5 Feb 2024 17:25:56 +0100 Subject: [PATCH 008/245] Release 7.107.1-1 (#2439) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2641a1ca42..ee4f8d2ec4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8202,7 +8202,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8239,7 +8239,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8331,7 +8331,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8359,7 +8359,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8509,7 +8509,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8535,7 +8535,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8600,7 +8600,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8635,7 +8635,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8669,7 +8669,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8700,7 +8700,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8987,7 +8987,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9018,7 +9018,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9047,7 +9047,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9081,7 +9081,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9112,7 +9112,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9145,11 +9145,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9387,7 +9387,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9414,7 +9414,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9447,7 +9447,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9485,7 +9485,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9521,7 +9521,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9556,11 +9556,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9734,11 +9734,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9767,10 +9767,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 8a39dc9c1dc5116b82beaae84c6c3e612a1bebaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 5 Feb 2024 19:00:13 +0100 Subject: [PATCH 009/245] Release 7.108.0-1 (#2440) Co-authored-by: Maxim Tsoy Co-authored-by: Daniel Bernal --- Core/UserDefaultsPropertyWrapper.swift | 3 +- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++---------- DuckDuckGo/Autoconsent/autoconsent-bundle.js | 2 +- DuckDuckGo/SettingsDebugView.swift | 16 +++--- package-lock.json | 8 +-- package.json | 2 +- scripts/prepare_release.sh | 10 ++-- 7 files changed, 50 insertions(+), 47 deletions(-) diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index bf12cc003f..7d4a96f316 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -83,7 +83,8 @@ public struct UserDefaultsWrapper { case autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = "com.duckduckgo.ios.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary" - case featureFlaggingDidVerifyInternalUser = "com.duckduckgo.app.featureFlaggingDidVerifyInternalUser" + // .v2 suffix added to fix https://app.asana.com/0/547792610048271/1206524375402369/f + case featureFlaggingDidVerifyInternalUser = "com.duckduckgo.app.featureFlaggingDidVerifyInternalUser.v2" case voiceSearchEnabled = "com.duckduckgo.app.voiceSearchEnabled" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 700b74eb7b..bd0d7e28f3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8158,7 +8158,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8195,7 +8195,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8287,7 +8287,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8315,7 +8315,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8465,7 +8465,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8491,7 +8491,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8556,7 +8556,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8591,7 +8591,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8625,7 +8625,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8656,7 +8656,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8943,7 +8943,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8974,7 +8974,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9003,7 +9003,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9037,7 +9037,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9068,7 +9068,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9101,11 +9101,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9343,7 +9343,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9370,7 +9370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9403,7 +9403,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9441,7 +9441,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9477,7 +9477,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9512,11 +9512,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9690,11 +9690,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9723,10 +9723,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index a61c1559d1..7775dafeb4 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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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_AXEPTIO_0:()=>document.cookie.includes("axeptio_authorized_vendors=%2C%2C"),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_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_COMPLIANZ_CATEGORIES_0:()=>!!document.cookie.match(/cmplz_[^=]+=deny/),EVAL_COMPLIANZ_OPTIN_0:()=>!!document.cookie.match(/cookieconsent_preferences_disabled=[^;]+/),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_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_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(/ezCMPCookieConsent=[^;]+\|2=0\|3=0\|4=0/),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_0:()=>document.cookie.includes("euconsent-v2"),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_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_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:()=>"_cookies_accepted=essential"===document.cookie,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_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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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))}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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}var g="#truste-show-consent",y="#truste-consent-track",w=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${y}`],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},${y}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${y}`,"all")}openFrame(){this.click(g)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${y}`),this.click(g),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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(1e3),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 d{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"),!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 d{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")||(k(h(),"#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 d{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")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk","all")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-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,.js-accept-cookies")}async test(){return await b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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")}async detectPopup(){return this.elementVisible(".unic .unic-box,.unic .unic-bar","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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],C=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var v=[{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:"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:"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:"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:"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:"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},{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:[{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:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{click:".cc-dismiss"}],test:[{eval:"EVAL_COMPLIANZ_CATEGORIES_0"}]},{name:"Complianz notice",prehideSelectors:['.cc-type-info[aria-describedby="cookieconsent:desc"]'],cosmetic:!0,detectCmp:[{exists:'.cc-type-info[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-info[aria-describedby="cookieconsent:desc"]'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{hide:'[aria-describedby="cookieconsent:desc"]'}]},{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:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{click:".cc-settings"},{waitForVisible:'[aria-label="cookies preferences popup"]'},{click:'[aria-label="cookies preferences popup"] input[type=checkbox]:not([disabled]):checked',all:!0,optional:!0},{click:'[aria-label="cookies preferences popup"] [aria-label="Accept Selected"], [aria-label="cookies preferences popup"] [aria-label="Save my choice"], .cc-btn-accept-selected, .cc-deny',optional:!0}],test:[{eval:"EVAL_COMPLIANZ_OPTIN_0"}]},{name:"cookie-law-info",prehideSelectors:["#cookie-law-info-bar"],detectCmp:[{exists:"#cookie-law-info-bar"}],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:"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:"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:"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:"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:".eu-cookie-compliance-banner-info"}],detectPopup:[{exists:".eu-cookie-compliance-popup-open"}],optIn:[{click:".agree-button"}],optOut:[{click:".decline-button,.eu-cookie-compliance-save-preferences-button",optional:!0},{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:"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:"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:"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:"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:"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:"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:"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",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:[{click:"#lanyard_root button[class^='link']",optional:!0},{if:{exists:"#lanyard_root button[class*='confirmButton']"},then:[{waitForThenClick:"#lanyard_root button[class*='rejectButton']"},{click:"#lanyard_root button[class*='confirmButton']"}],else:[{click:"#lanyard_root div[class^='buttons'] > :nth-child(1)",optional:!0},{waitForThenClick:"#lanyard_root input:checked"},{click:"#consentsTab > div:nth-child(2) > div > div[class^='actions'] > button:nth-child(1)"}]}],test:[]},{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:"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:"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:"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:"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"],cosmetic:!0,detectCmp:[{exists:".osano-cm-window"}],detectPopup:[{visible:".osano-cm-dialog"}],optIn:[{click:".osano-cm-accept-all",optional:!0}],optOut:[{hide:".osano-cm-window"}]},{name:"otto.de",prehideSelectors:[".cookieBanner--visibility"],detectCmp:[{exists:".cookieBanner--visibility"}],detectPopup:[{visible:".cookieBanner__wrapper"}],optIn:[{click:".js_cookieBannerPermissionButton"}],optOut:[{click:".js_cookieBannerProhibitionButton"}]},{name:"paypal-us",prehideSelectors:["#ccpaCookieContent_wrapper, article.ppvx_modal--overpanel"],detectCmp:[{exists:"#ccpaCookieBanner, .privacy-modal-content"}],detectPopup:[{exists:"#ccpaCookieBanner, .privacy-modal-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"a#manageCookiesLink"},then:[{click:"a#manageCookiesLink"}],else:[{waitForVisible:".privacy-modal-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:"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:['section:has(a[href^="https://www.reddit.com/policies/cookies"])'],detectCmp:[{exists:'section:has(a[href^="https://www.reddit.com/policies/cookies"])'}],detectPopup:[{visible:'section:has(a[href^="https://www.reddit.com/policies/cookies"])'}],optIn:[{waitForThenClick:"section:has(a[href^=\"https://www.reddit.com/policies/cookies\"]) section[class^='_'] > section:first-child form button"}],optOut:[{waitForThenClick:"section:has(a[href^=\"https://www.reddit.com/policies/cookies\"]) section[class^='_'] > section:last-child form button"}],test:[{eval:"EVAL_REDDIT_0"}]},{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:"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",prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:"#sd-cmp .sd-cmp-1pO44"}],test:[{eval:"EVAL_SIRDATA_0"}]},{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:"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:"Tealium",prehideSelectors:["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#consent-layer"],detectCmp:[{exists:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *"},{eval:"EVAL_TEALIUM_0"}],detectPopup:[{visible:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *",check:"any"}],optOut:[{eval:"EVAL_TEALIUM_1"},{eval:"EVAL_TEALIUM_DONOTSELL"},{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs"},{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:"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:"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:"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.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.p-button"},{waitForThenClick:'xpath///*[@id="cookie-policy-content"]/p[4]/button[2]'}]},{waitForThenClick:".p-switch__input:checked",optional:!0,all:!0},{any:[{waitForThenClick:"div > button"},{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:"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:"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:"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:"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:[]}],f={"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"}]}},A={autoconsent:v,consentomatic:f},E=Object.freeze({__proto__:null,autoconsent:v,consentomatic:f,default:A});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 C(this)}initialize(e,t){const o=function(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=structuredClone(t);for(const c of Object.keys(t))void 0!==e[c]&&(o[c]=e[c]);return o}(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(){w.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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"});let o=await this.detectPopups(t.filter((e=>!e.isCosmetic)));if(0===o.length&&(o=await this.detectPopups(t.filter((e=>e.isCosmetic)))),0===o.length)return e.lifecycle&&console.log("no popup found"),this.config.enablePrehide&&this.undoPrehide(),!1;if(this.updateState({lifecycle:"openPopupDetected"}),this.config.enablePrehide&&!this.state.prehideOn&&this.prehideElements(),o.length>1){const t={msg:"Found multiple CMPs, check the detection rules.",cmps:o.map((e=>e.name))};e.errors&&console.warn(t.msg,t.cmps),this.sendContentMessage({type:"autoconsentError",details:t})}return this.foundCmp=o[0],"optOut"===this.config.autoAction?await this.doOptOut():"optIn"===this.config.autoAction?await this.doOptIn():(e.lifecycle&&console.log("waiting for opt-out signal...",location.href),!0)}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 detectPopups(e){const t=this.config.logs,o=[],c=e.map((e=>this.waitForPopup(e).then((t=>{t&&(this.updateState({detectedPopups:this.state.detectedPopups.concat([e.name])}),this.sendContentMessage({type:"popupFound",cmp:e.name,url:location.href}),o.push(e))})).catch((o=>(t.errors&&console.warn(`error waiting for a popup for ${e.name}`,o),null)))));return await Promise.all(c),o}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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,E);window.autoconsentMessageCallback=e=>{O.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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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_0:()=>document.cookie.includes("euconsent-v2"),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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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 g(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 y="#truste-show-consent",w="#truste-consent-track",C=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${w}`],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(`${y},${w}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${w}`,"all")}openFrame(){this.click(y)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${w}`),this.click(y),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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 d{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 d{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")||(k(h(),"#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 d{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")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk","all")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-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,.js-accept-cookies")}async test(){return await b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],v=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var f=[{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},{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:[{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",prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:"#sd-cmp .sd-cmp-1pO44"}],test:[{eval:"EVAL_SIRDATA_0"}]},{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:[]}],A={"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"}]}},E={autoconsent:f,consentomatic:A},x=Object.freeze({__proto__:null,autoconsent:f,consentomatic:A,default:E});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 v(this)}initialize(e,t){const o=g(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(){C.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,x);window.autoconsentMessageCallback=e=>{O.receiveMessageCallback(e)}}(); diff --git a/DuckDuckGo/SettingsDebugView.swift b/DuckDuckGo/SettingsDebugView.swift index 39615314c2..8baacecbf5 100644 --- a/DuckDuckGo/SettingsDebugView.swift +++ b/DuckDuckGo/SettingsDebugView.swift @@ -25,13 +25,15 @@ struct SettingsDebugView: View { @EnvironmentObject var viewModel: SettingsViewModel var body: some View { - Section(header: Text("Debug")) { - - SettingsCellView(label: "All debug options", - action: { viewModel.presentLegacyView(.debug) }, - disclosureIndicator: true, - isButton: true) - + if viewModel.state.debugModeEnabled { + Section(header: Text("Debug")) { + + SettingsCellView(label: "All debug options", + action: { viewModel.presentLegacyView(.debug) }, + disclosureIndicator: true, + isButton: true) + + } } } diff --git a/package-lock.json b/package-lock.json index 6ba27d8bf1..2947f69fc5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "ios", "version": "1.0.0", "dependencies": { - "@duckduckgo/autoconsent": "^9.1.0" + "@duckduckgo/autoconsent": "^9.7.2" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", @@ -135,9 +135,9 @@ } }, "node_modules/@duckduckgo/autoconsent": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-9.1.0.tgz", - "integrity": "sha512-zjuYu+Wb/8cscFrjSttA4uMjxwcRAzPcHmou5vgbYAfEV7qdsAPMvuai2REke199BmoI7OK2khtsLq7LGqkDzQ==" + "version": "9.7.2", + "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-9.7.2.tgz", + "integrity": "sha512-LaJdRoZwRneDPzzVQdiHLPNjMW6B773edisKXiWYUXZOIXzGbwifKjCiYHTtDtaeaLjpuiWXlz2a9yEwMszK2A==" }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", diff --git a/package.json b/package.json index d0973a9923..3224a7c33c 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ "rollup-plugin-terser": "^7.0.2" }, "dependencies": { - "@duckduckgo/autoconsent": "^9.1.0" + "@duckduckgo/autoconsent": "^9.7.2" } } diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index 4a5bde66d3..d413781f84 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -153,6 +153,11 @@ create_build_branch() { local temp_file local latest_build_number + if [[ $is_hotfix ]]; then + version=$(cut -d' ' -f3 < "${base_dir}/Configuration/Version.xcconfig") + version=$(bump_patch_number "$version") + fi + temp_file=$(mktemp) bundle exec fastlane latest_build_number_for_version version:"$version" file_name:"$temp_file" latest_build_number="$(<"$temp_file")" @@ -171,11 +176,6 @@ create_build_branch() { update_marketing_version() { printf '%s' "Setting app version ... " - if [[ $is_hotfix ]]; then - version=$(cut -d' ' -f3 < "${base_dir}/Configuration/Version.xcconfig") - version=$(bump_patch_number "$version") - fi - "$script_dir/set_version.sh" "${version}" git add "${base_dir}/Configuration/Version.xcconfig" \ "${base_dir}/DuckDuckGo/Settings.bundle/Root.plist" From cef5f51c288badeb57ad5f7136f80ab2f71f2833 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Tue, 6 Feb 2024 12:07:33 +0000 Subject: [PATCH 010/245] Keep DuckDuckGo Settings after Fire Button (#2444) --- Core/CookieStorage.swift | 2 +- Core/WebCacheManager.swift | 10 ++-- DuckDuckGoTests/CookieStorageTests.swift | 6 +++ .../FireButtonReferenceTests.swift | 50 +++++++++++++++++-- DuckDuckGoTests/WebCacheManagerTests.swift | 8 +-- 5 files changed, 61 insertions(+), 15 deletions(-) diff --git a/Core/CookieStorage.swift b/Core/CookieStorage.swift index 5624827ee1..0a83d6cba1 100644 --- a/Core/CookieStorage.swift +++ b/Core/CookieStorage.swift @@ -114,7 +114,7 @@ public class CookieStorage { } persistedCookiesByDomain.keys.forEach { - guard $0 != "duckduckgo.com" else { return } // DDG cookies are for SERP settings only + guard !URL.isDuckDuckGo(domain: $0) else { return } // DDG cookies are for SERP settings only if !preservedLogins.isAllowed(cookieDomain: $0) { persistedCookiesByDomain.removeValue(forKey: $0) diff --git a/Core/WebCacheManager.swift b/Core/WebCacheManager.swift index 2c66cdd06b..d9b91bb2f9 100644 --- a/Core/WebCacheManager.swift +++ b/Core/WebCacheManager.swift @@ -56,10 +56,6 @@ extension WebCacheManagerDataStore { } public class WebCacheManager { - - private struct Constants { - static let cookieDomainsToPreserve = ["duckduckgo.com", "surveys.duckduckgo.com"] - } public static var shared = WebCacheManager() @@ -223,7 +219,7 @@ public class WebCacheManager { func keep(_ cookie: HTTPCookie) -> Bool { return logins.isAllowed(cookieDomain: cookie.domain) || - Constants.cookieDomainsToPreserve.contains(cookie.domain) + URL.isDuckDuckGo(domain: cookie.domain) } let dataStore = WKWebsiteDataStore.default() @@ -270,7 +266,7 @@ public class WebCacheManager { // Remove legacy HTTPCookieStorage cookies let storageCookies = HTTPCookieStorage.shared.cookies ?? [] let storageCookiesToRemove = storageCookies.filter { - !logins.isAllowed(cookieDomain: $0.domain) && !Constants.cookieDomainsToPreserve.contains($0.domain) + !logins.isAllowed(cookieDomain: $0.domain) && !URL.isDuckDuckGo(domain: $0.domain) } let protectedStorageCookiesCount = storageCookies.count - storageCookiesToRemove.count @@ -389,7 +385,7 @@ extension WKWebsiteDataStore: WebCacheManagerDataStore { public func preservedCookies(_ preservedLogins: PreserveLogins) async -> [HTTPCookie] { let allCookies = await self.httpCookieStore.allCookies() return allCookies.filter { - preservedLogins.isAllowed(cookieDomain: $0.domain) + URL.isDuckDuckGo(domain: $0.domain) || preservedLogins.isAllowed(cookieDomain: $0.domain) } } diff --git a/DuckDuckGoTests/CookieStorageTests.swift b/DuckDuckGoTests/CookieStorageTests.swift index 79b0dc2751..4a78e3e006 100644 --- a/DuckDuckGoTests/CookieStorageTests.swift +++ b/DuckDuckGoTests/CookieStorageTests.swift @@ -51,6 +51,12 @@ public class CookieStorageTests: XCTestCase { XCTAssertEqual(2, storage.cookies.count) + storage.updateCookies([ + make("usedev1.duckduckgo.com", name: "x", value: "1"), + ], keepingPreservedLogins: logins) + + XCTAssertEqual(3, storage.cookies.count) + } func testWhenUpdatedThenCookiesWithFutureExpirationAreNotRemoved() { diff --git a/DuckDuckGoTests/FireButtonReferenceTests.swift b/DuckDuckGoTests/FireButtonReferenceTests.swift index 2f04f6fbc3..6e1ea46555 100644 --- a/DuckDuckGoTests/FireButtonReferenceTests.swift +++ b/DuckDuckGoTests/FireButtonReferenceTests.swift @@ -44,7 +44,7 @@ final class FireButtonReferenceTests: XCTestCase { return url.host! } - func testCookieStorage() { + func testClearData() async throws { let preservedLogins = PreserveLogins.shared preservedLogins.clearAll() @@ -59,12 +59,54 @@ final class FireButtonReferenceTests: XCTestCase { } let cookieStorage = CookieStorage() + let idManager = DataStoreIdManager() for test in referenceTests { - guard let cookie = cookie(for: test) else { - XCTFail("Cookie should exist for test \(test.name)") - return + let cookie = try XCTUnwrap(cookie(for: test)) + + // Set directly to avoid logic to remove non-preserved cookies + cookieStorage.cookies = [ + cookie + ] + + idManager.allocateNewContainerId() + await withCheckedContinuation { continuation in + WebCacheManager.shared.clear(cookieStorage: cookieStorage, logins: preservedLogins, dataStoreIdManager: idManager) { + continuation.resume() + } } + let testCookie = cookieStorage.cookies.filter { $0.name == test.cookieName }.first + + if test.expectCookieRemoved { + XCTAssertNil(testCookie, "Cookie should not exist for test: \(test.name)") + } else { + XCTAssertNotNil(testCookie, "Cookie should exist for test: \(test.name)") + } + + // Reset cache + cookieStorage.cookies = [] + } + + } + + func testCookieStorage() throws { + let preservedLogins = PreserveLogins.shared + preservedLogins.clearAll() + + for site in testData.fireButtonFireproofing.fireproofedSites { + let sanitizedSite = sanitizedSite(site) + os_log("Adding %s to fireproofed sites", sanitizedSite) + preservedLogins.addToAllowed(domain: sanitizedSite) + } + + let referenceTests = testData.fireButtonFireproofing.tests.filter { + $0.exceptPlatforms.contains("ios-browser") == false + } + + let cookieStorage = CookieStorage() + for test in referenceTests { + let cookie = try XCTUnwrap(cookie(for: test)) + cookieStorage.updateCookies([ cookie ], keepingPreservedLogins: preservedLogins) diff --git a/DuckDuckGoTests/WebCacheManagerTests.swift b/DuckDuckGoTests/WebCacheManagerTests.swift index 488960990e..b0c3dc2b2c 100644 --- a/DuckDuckGoTests/WebCacheManagerTests.swift +++ b/DuckDuckGoTests/WebCacheManagerTests.swift @@ -128,7 +128,8 @@ class WebCacheManagerTests: XCTestCase { let dataStore = MockDataStore() let cookieStore = MockHTTPCookieStore(cookies: [ - .make(domain: "duckduckgo.com") + .make(domain: "duckduckgo.com"), + .make(domain: "subdomain.duckduckgo.com") ]) dataStore.cookieStore = cookieStore @@ -139,8 +140,9 @@ class WebCacheManagerTests: XCTestCase { } wait(for: [expect], timeout: 5.0) - XCTAssertEqual(cookieStore.cookies.count, 1) - XCTAssertEqual(cookieStore.cookies[0].domain, "duckduckgo.com") + XCTAssertEqual(cookieStore.cookies.count, 2) + XCTAssertTrue(cookieStore.cookies.contains(where: { $0.domain == "duckduckgo.com" })) + XCTAssertTrue(cookieStore.cookies.contains(where: { $0.domain == "subdomain.duckduckgo.com" })) } @MainActor From 6bf3c891548844bef030b33c8f80b790eceb75a0 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Tue, 6 Feb 2024 12:24:08 +0000 Subject: [PATCH 011/245] Release 7.108.0-2 (#2445) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bd0d7e28f3..c20d3e2966 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8158,7 +8158,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8195,7 +8195,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8287,7 +8287,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8315,7 +8315,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8465,7 +8465,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8491,7 +8491,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8556,7 +8556,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8591,7 +8591,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8625,7 +8625,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8656,7 +8656,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8943,7 +8943,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8974,7 +8974,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9003,7 +9003,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9037,7 +9037,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9068,7 +9068,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9101,11 +9101,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9343,7 +9343,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9370,7 +9370,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9403,7 +9403,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9441,7 +9441,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9477,7 +9477,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9512,11 +9512,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9690,11 +9690,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9723,10 +9723,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 1b03c08f3710c6ee2e8635ec6a80e5175195f1d7 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 6 Feb 2024 12:39:17 -0800 Subject: [PATCH 012/245] Remove the mock VPN invite code (#2442) Task/Issue URL: https://app.asana.com/0/414235014887631/1206526539842944/f Tech Design URL: CC: Description: This PR removes the mock invite code set for VPN users who had access to the debug menu. --- DuckDuckGo/AppDelegate+Waitlists.swift | 9 +++++++++ DuckDuckGo/AppDelegate.swift | 2 ++ DuckDuckGo/VPNWaitlistDebugViewController.swift | 8 ++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index b377d170b7..5c1c37c51f 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -21,9 +21,18 @@ import Foundation import Core import BackgroundTasks import NetworkProtection +import Waitlist extension AppDelegate { + func clearDebugWaitlistState() { + if let inviteCode = VPNWaitlist.shared.waitlistStorage.getWaitlistInviteCode(), + inviteCode == VPNWaitlistDebugViewController.Constants.mockInviteCode { + let store = WaitlistKeychainStore(waitlistIdentifier: VPNWaitlist.identifier) + store.delete(field: .inviteCode) + } + } + func checkWaitlists() { checkWindowsWaitlist() diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index e997dcd121..bc1a067368 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -322,6 +322,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { setupSubscriptionsEnvironment() #endif + clearDebugWaitlistState() + return true } diff --git a/DuckDuckGo/VPNWaitlistDebugViewController.swift b/DuckDuckGo/VPNWaitlistDebugViewController.swift index a206f38631..9ca0438b59 100644 --- a/DuckDuckGo/VPNWaitlistDebugViewController.swift +++ b/DuckDuckGo/VPNWaitlistDebugViewController.swift @@ -26,6 +26,10 @@ import Waitlist final class VPNWaitlistDebugViewController: UITableViewController { + enum Constants { + static let mockInviteCode = "ABCD1234" + } + enum Sections: Int, CaseIterable { case waitlistInformation @@ -167,11 +171,11 @@ final class VPNWaitlistDebugViewController: UITableViewController { termsAndConditionsStore.networkProtectionWaitlistTermsAndConditionsAccepted = false case .scheduleWaitlistNotification: DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { - self.storage.store(inviteCode: "ABCD1234") + self.storage.store(inviteCode: Constants.mockInviteCode) VPNWaitlist.shared.sendInviteCodeAvailableNotification() } case .setMockInviteCode: - storage.store(inviteCode: "ABCD1234") + storage.store(inviteCode: Constants.mockInviteCode) case .deleteInviteCode: storage.delete(field: .inviteCode) tableView.reloadData() From 3d084363f9d61cd34c43946d3c2cee0b3806f780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Wed, 7 Feb 2024 12:01:29 +0100 Subject: [PATCH 013/245] Check subfeature state before reading rollout data (#2426) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c20d3e2966..4e5ffa634d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9933,7 +9933,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 104.1.1; + version = 104.2.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3cf6906a27..481abcc96c 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "3b10ff8d5d433a4eb45a1d4d25a348033f5102c1", - "version" : "104.1.1" + "branch" : "mariusz/fix-rollout-check", + "revision" : "b11b744048cf0538ff04ee434772fde0ff81f9d7" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "03d3e3a959dd75afbe8c59b5a203ea676d37555d", - "version" : "10.1.0" + "revision" : "b972bc0ab6ee1d57a0a18a197dcc31e40ae6ac57", + "version" : "10.0.3" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index f157cebcee..d33c33c8bb 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.2.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index b56372728e..82e67f3956 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.2.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 7fcae24082..a70b7d8a56 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.1.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.2.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From fa70669ff992f80427f42061fdfc940ed34481be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Wed, 7 Feb 2024 14:52:40 +0100 Subject: [PATCH 014/245] Set proper border color for widgets (#2446) --- Widgets/WidgetViews.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Widgets/WidgetViews.swift b/Widgets/WidgetViews.swift index 53cb8b1bb7..034d189ceb 100644 --- a/Widgets/WidgetViews.swift +++ b/Widgets/WidgetViews.swift @@ -206,7 +206,7 @@ struct FavoritesWidgetView: View { .padding(EdgeInsets(top: widgetFamily == .systemLarge ? 48 : 60, leading: 0, bottom: 0, trailing: 0)) } - .widgetContainerBackground() + .widgetContainerBackground(color: .widgetBackground) } } @@ -240,17 +240,17 @@ struct SearchWidgetView: View { } }.accessibilityHidden(true) } - .widgetContainerBackground() + .widgetContainerBackground(color: .widgetBackground) } } // See https://stackoverflow.com/a/59228385/73479 extension View { - @ViewBuilder func widgetContainerBackground() -> some View { + @ViewBuilder func widgetContainerBackground(color: Color = .clear) -> some View { if #available(iOSApplicationExtension 17.0, *) { containerBackground(for: .widget) { - Color.clear + color } } } From 4b9758c517cb31f024afe828acab664db0092b3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Wed, 7 Feb 2024 16:34:23 +0100 Subject: [PATCH 015/245] Fix BSK reference to tagged version (#2447) --- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 481abcc96c..608fcb096b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "branch" : "mariusz/fix-rollout-check", - "revision" : "b11b744048cf0538ff04ee434772fde0ff81f9d7" + "revision" : "2555be729ff019465b4b263d9d68c2d10a88470a", + "version" : "104.2.1" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "b972bc0ab6ee1d57a0a18a197dcc31e40ae6ac57", - "version" : "10.0.3" + "revision" : "03d3e3a959dd75afbe8c59b5a203ea676d37555d", + "version" : "10.1.0" } }, { From f3890842a883a68b7150b29d67e438739c383048 Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Thu, 8 Feb 2024 10:34:24 +0100 Subject: [PATCH 016/245] User Authentication (#2431) Task/Issue URL: https://app.asana.com/0/1201493110486074/1206488462322475/f Tech Design URL: CC: Description: Add iOS user authentication to sync flows. --- DuckDuckGo.xcodeproj/project.pbxproj | 8 ++ .../AutofillLoginDetailsViewController.swift | 2 +- .../AutofillLoginListAuthenticator.swift | 66 ++------------ DuckDuckGo/AutofillLoginListViewModel.swift | 4 +- .../AutofillLoginPromptViewController.swift | 2 +- DuckDuckGo/AutofillLoginSession.swift | 29 +----- ...cSettingsViewController+PDFRendering.swift | 14 ++- ...cSettingsViewController+SyncDelegate.swift | 51 ++++++++--- DuckDuckGo/SyncSettingsViewController.swift | 16 ++++ DuckDuckGo/UserAuthenticator.swift | 91 +++++++++++++++++++ DuckDuckGo/UserSession.swift | 49 ++++++++++ DuckDuckGo/UserText.swift | 1 + DuckDuckGo/en.lproj/Localizable.strings | 3 + .../AutofillLoginSessionTests.swift | 8 +- .../SyncManagementViewModelTests.swift | 12 ++- .../ViewModels/SyncSettingsViewModel.swift | 5 + .../Views/SyncSettingsViewExtension.swift | 8 +- 17 files changed, 252 insertions(+), 117 deletions(-) create mode 100644 DuckDuckGo/UserAuthenticator.swift create mode 100644 DuckDuckGo/UserSession.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c20d3e2966..a4a08b9bcf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -530,6 +530,8 @@ 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 981FED75220464EF008488D7 /* AutoClearSettingsModel.swift */; }; 9820EAF522613CD30089094D /* WebProgressWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9820EAF422613CD30089094D /* WebProgressWorker.swift */; }; 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9820FF4F2244FECC008D4782 /* UIScrollViewExtension.swift */; }; + 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */; }; + 982123502B6D233E00F08C57 /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9821234F2B6D233E00F08C57 /* UserSession.swift */; }; 9825F9DB293F2E8700F220F2 /* BookmarksTestData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9825F9DA293F2E8700F220F2 /* BookmarksTestData.swift */; }; 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 982686AC2600C0850011A8D6 /* ActionMessageView.swift */; }; 982686B92600C0960011A8D6 /* ActionMessageView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 982686B82600C0960011A8D6 /* ActionMessageView.xib */; }; @@ -1656,6 +1658,8 @@ 9820A5D522B1C0B20024E37C /* DDG Trace.tracetemplate */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; path = "DDG Trace.tracetemplate"; sourceTree = ""; }; 9820EAF422613CD30089094D /* WebProgressWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebProgressWorker.swift; sourceTree = ""; }; 9820FF4F2244FECC008D4782 /* UIScrollViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollViewExtension.swift; sourceTree = ""; }; + 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserAuthenticator.swift; sourceTree = ""; }; + 9821234F2B6D233E00F08C57 /* UserSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSession.swift; sourceTree = ""; }; 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PerformanceTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9825F9DA293F2E8700F220F2 /* BookmarksTestData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksTestData.swift; sourceTree = ""; }; 982686AC2600C0850011A8D6 /* ActionMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionMessageView.swift; sourceTree = ""; }; @@ -5357,6 +5361,8 @@ 983EABB7236198F6003948D1 /* DatabaseMigration.swift */, 853C5F6021C277C7001F7A05 /* global.swift */, 85C8E61C2B0E47380029A6BD /* BookmarksDatabaseSetup.swift */, + 9821234D2B6D0A6300F08C57 /* UserAuthenticator.swift */, + 9821234F2B6D233E00F08C57 /* UserSession.swift */, ); name = Application; sourceTree = ""; @@ -6765,12 +6771,14 @@ C1F341C52A6924000032057B /* EmailAddressPromptView.swift in Sources */, 316931D727BD10BB0095F5ED /* SaveToDownloadsAlert.swift in Sources */, 31C70B5B2804C61000FB6AD1 /* SaveAutofillLoginManager.swift in Sources */, + 982123502B6D233E00F08C57 /* UserSession.swift in Sources */, 85449EFD23FDA71F00512AAF /* KeyboardSettings.swift in Sources */, 980891A222369ADB00313A70 /* FeedbackUserText.swift in Sources */, 4BCD14692B05BDD5000B1E4C /* AppDelegate+Waitlists.swift in Sources */, 988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */, 850ABD012AC3961100A733DF /* MainViewController+Segues.swift in Sources */, 9817C9C321EF594700884F65 /* AutoClear.swift in Sources */, + 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */, 310C4B47281B60E300BA79A9 /* AutofillLoginDetailsViewModel.swift in Sources */, 85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */, 1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */, diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index 73903f128f..c44a48f94f 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -37,7 +37,7 @@ class AutofillLoginDetailsViewController: UIViewController { weak var delegate: AutofillLoginDetailsViewControllerDelegate? private let viewModel: AutofillLoginDetailsViewModel private var cancellables: Set = [] - private var authenticator = AutofillLoginListAuthenticator() + private var authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) private let lockedView = AutofillItemsLockedView() private let noAuthAvailableView = AutofillNoAuthAvailableView() private var contentView: UIView? diff --git a/DuckDuckGo/AutofillLoginListAuthenticator.swift b/DuckDuckGo/AutofillLoginListAuthenticator.swift index ab047227a5..158583661b 100644 --- a/DuckDuckGo/AutofillLoginListAuthenticator.swift +++ b/DuckDuckGo/AutofillLoginListAuthenticator.swift @@ -22,68 +22,16 @@ import Foundation import LocalAuthentication import Core -final class AutofillLoginListAuthenticator { - enum AuthError: Equatable { - case noAuthAvailable - case failedToAuthenticate - } - - enum AuthenticationState { - case loggedIn, loggedOut, notAvailable - } - - public struct Notifications { - public static let invalidateContext = Notification.Name("com.duckduckgo.app.AutofillLoginListAuthenticator.invalidateContext") - } - - private var context = LAContext() - @Published private(set) var state = AuthenticationState.loggedOut - - func logOut() { - state = .loggedOut - } +final class AutofillLoginListAuthenticator: UserAuthenticator { - func canAuthenticate() -> Bool { - var error: NSError? - let canAuthenticate = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) - return canAuthenticate - } + override func authenticate(completion: ((AuthError?) -> Void)? = nil) { - func authenticate(completion: ((AuthError?) -> Void)? = nil) { - - if state == .loggedIn { - completion?(nil) - return - } - - context = LAContext() - context.localizedCancelTitle = UserText.autofillLoginListAuthenticationCancelButton - let reason = UserText.autofillLoginListAuthenticationReason - context.localizedReason = reason - - if canAuthenticate() { - let reason = reason - context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, error in - - DispatchQueue.main.async { - if success { - self.state = .loggedIn - completion?(nil) - } else { - os_log("Failed to authenticate: %s", log: .generalLog, type: .debug, error?.localizedDescription ?? "nil error") - AppDependencyProvider.shared.autofillLoginSession.endSession() - completion?(.failedToAuthenticate) - } - } + super.authenticate { error in + if error != nil { + AppDependencyProvider.shared.autofillLoginSession.endSession() } - } else { - state = .notAvailable - AppDependencyProvider.shared.autofillLoginSession.endSession() - completion?(.noAuthAvailable) - } - } - func invalidateContext() { - context.invalidate() + completion?(error) + } } } diff --git a/DuckDuckGo/AutofillLoginListViewModel.swift b/DuckDuckGo/AutofillLoginListViewModel.swift index 2295c54873..20abb86e80 100644 --- a/DuckDuckGo/AutofillLoginListViewModel.swift +++ b/DuckDuckGo/AutofillLoginListViewModel.swift @@ -61,7 +61,7 @@ final class AutofillLoginListViewModel: ObservableObject { case searchingNoResults } - let authenticator = AutofillLoginListAuthenticator() + let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) var isSearching: Bool = false var authenticationNotRequired = false private var accounts = [SecureVaultModels.WebsiteAccount]() @@ -104,7 +104,7 @@ final class AutofillLoginListViewModel: ObservableObject { self.autofillNeverPromptWebsitesManager = autofillNeverPromptWebsitesManager updateData() - authenticationNotRequired = !hasAccountsSaved || AppDependencyProvider.shared.autofillLoginSession.isValidSession + authenticationNotRequired = !hasAccountsSaved || AppDependencyProvider.shared.autofillLoginSession.isSessionValid setupCancellables() } diff --git a/DuckDuckGo/AutofillLoginPromptViewController.swift b/DuckDuckGo/AutofillLoginPromptViewController.swift index 1133e169d2..d2c78acb49 100644 --- a/DuckDuckGo/AutofillLoginPromptViewController.swift +++ b/DuckDuckGo/AutofillLoginPromptViewController.swift @@ -112,7 +112,7 @@ extension AutofillLoginPromptViewController: AutofillLoginPromptViewModelDelegat Pixel.fire(pixel: .autofillLoginsFillLoginInlineManualConfirmed) } - if AppDependencyProvider.shared.autofillLoginSession.isValidSession { + if AppDependencyProvider.shared.autofillLoginSession.isSessionValid { dismiss(animated: true, completion: nil) completion?(account, false) return diff --git a/DuckDuckGo/AutofillLoginSession.swift b/DuckDuckGo/AutofillLoginSession.swift index 6c9fb84ff7..8249fab110 100644 --- a/DuckDuckGo/AutofillLoginSession.swift +++ b/DuckDuckGo/AutofillLoginSession.swift @@ -20,42 +20,21 @@ import Foundation import BrowserServicesKit -class AutofillLoginSession { +final class AutofillLoginSession: UserSession { - private enum Constants { - static let timeout: TimeInterval = 15 - } - - private var sessionCreationDate: Date? private var sessionAccount: SecureVaultModels.WebsiteAccount? - private let sessionTimeout: TimeInterval - - init(sessionTimeout: TimeInterval = Constants.timeout) { - self.sessionTimeout = sessionTimeout - } - - var isValidSession: Bool { - guard let sessionCreationDate = sessionCreationDate else { return false } - let timeInterval = Date().timeIntervalSince(sessionCreationDate) - // Check that timeInterval is > 0 to prevent a user circumventing by changing their device clock time - return timeInterval > 0 && timeInterval < sessionTimeout - } var lastAccessedAccount: SecureVaultModels.WebsiteAccount? { get { - return isValidSession ? sessionAccount : nil + return isSessionValid ? sessionAccount : nil } set { sessionAccount = newValue } } - func startSession() { - sessionCreationDate = Date() - } - - func endSession() { - sessionCreationDate = nil + override func endSession() { + super.endSession() lastAccessedAccount = nil } } diff --git a/DuckDuckGo/SyncSettingsViewController+PDFRendering.swift b/DuckDuckGo/SyncSettingsViewController+PDFRendering.swift index af406c6526..0a1a6e9d17 100644 --- a/DuckDuckGo/SyncSettingsViewController+PDFRendering.swift +++ b/DuckDuckGo/SyncSettingsViewController+PDFRendering.swift @@ -26,12 +26,16 @@ extension SyncSettingsViewController { func shareRecoveryPDF() { - let data = RecoveryPDFGenerator() - .generate(recoveryCode) + authenticateUser { [weak self] error in + guard error == nil, let self else { return } - let pdf = RecoveryCodeItem(data: data) - navigationController?.visibleViewController?.presentShareSheet(withItems: [pdf], - fromView: view) + let data = RecoveryPDFGenerator() + .generate(recoveryCode) + + let pdf = RecoveryCodeItem(data: data) + navigationController?.visibleViewController?.presentShareSheet(withItems: [pdf], + fromView: view) + } } func shareCode(_ code: String) { diff --git a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift index 6ec419d7a2..1209d011a3 100644 --- a/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift +++ b/DuckDuckGo/SyncSettingsViewController+SyncDelegate.swift @@ -26,6 +26,18 @@ import AVFoundation extension SyncSettingsViewController: SyncManagementViewModelDelegate { + func authenticateUser() async -> Bool { + return await withCheckedContinuation { continuation in + authenticateUser { error in + if error == nil { + continuation.resume(returning: true) + } else { + continuation.resume(returning: false) + } + } + } + } + func launchAutofillViewController() { guard let mainVC = view.window?.rootViewController as? MainViewController else { return } dismiss(animated: true) @@ -53,17 +65,20 @@ extension SyncSettingsViewController: SyncManagementViewModelDelegate { } func createAccountAndStartSyncing(optionsViewModel: SyncSettingsViewModel) { - Task { @MainActor in - do { - self.dismissPresentedViewController() - self.showPreparingSync() - try await syncService.createAccount(deviceName: deviceName, deviceType: deviceType) - Pixel.fire(pixel: .syncSignupDirect, includedParameters: [.appVersion]) - self.rootView.model.syncEnabled(recoveryCode: recoveryCode) - self.refreshDevices() - navigationController?.topViewController?.dismiss(animated: true, completion: showRecoveryPDF) - } catch { - handleError(SyncErrorMessage.unableToSyncToServer, error: error, event: .syncSignupError) + authenticateUser { [weak self] error in + guard error == nil, let self else { return } + Task { @MainActor in + do { + self.dismissPresentedViewController() + self.showPreparingSync() + try await self.syncService.createAccount(deviceName: self.deviceName, deviceType: self.deviceType) + Pixel.fire(pixel: .syncSignupDirect, includedParameters: [.appVersion]) + self.rootView.model.syncEnabled(recoveryCode: self.recoveryCode) + self.refreshDevices() + self.navigationController?.topViewController?.dismiss(animated: true, completion: self.showRecoveryPDF) + } catch { + self.handleError(SyncErrorMessage.unableToSyncToServer, error: error, event: .syncSignupError) + } } } } @@ -103,12 +118,20 @@ extension SyncSettingsViewController: SyncManagementViewModelDelegate { } func showSyncWithAnotherDevice() { - collectCode(showConnectMode: true) + authenticateUser { [weak self] error in + guard error == nil, let self else { return } + + self.collectCode(showConnectMode: true) + } } func showRecoverData() { - dismissPresentedViewController() - collectCode(showConnectMode: false) + authenticateUser { [weak self] error in + guard error == nil, let self else { return } + + self.dismissPresentedViewController() + self.collectCode(showConnectMode: false) + } } func showDeviceConnected() { diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index f49d56003c..7db8cd9667 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -33,6 +33,9 @@ class SyncSettingsViewController: UIHostingController { let syncBookmarksAdapter: SyncBookmarksAdapter var connector: RemoteConnecting? + let userAuthenticator = UserAuthenticator(reason: UserText.syncUserUserAuthenticationReason) + let userSession = UserSession() + var recoveryCode: String { guard let code = syncService.account?.recoveryCode else { return "" @@ -88,6 +91,19 @@ class SyncSettingsViewController: UIHostingController { fatalError("init(coder:) has not been implemented") } + func authenticateUser(completion: @escaping (UserAuthenticator.AuthError?) -> Void) { + if !userSession.isSessionValid { + userAuthenticator.logOut() + } + + userAuthenticator.authenticate { [weak self] error in + if error == nil { + self?.userSession.startSession() + } + completion(error) + } + } + private func setUpSyncFeatureFlags(_ viewModel: SyncSettingsViewModel) { syncService.featureFlagsPublisher.prepend(syncService.featureFlags) .removeDuplicates() diff --git a/DuckDuckGo/UserAuthenticator.swift b/DuckDuckGo/UserAuthenticator.swift new file mode 100644 index 0000000000..52ab8ae8fb --- /dev/null +++ b/DuckDuckGo/UserAuthenticator.swift @@ -0,0 +1,91 @@ +// +// UserAuthenticator.swift +// DuckDuckGo +// +// 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 Common +import Foundation +import LocalAuthentication +import Core + +class UserAuthenticator { + enum AuthError: Equatable { + case noAuthAvailable + case failedToAuthenticate + } + + enum AuthenticationState { + case loggedIn, loggedOut, notAvailable + } + + public struct Notifications { + public static let invalidateContext = Notification.Name("com.duckduckgo.app.UserAuthenticator.invalidateContext") + } + + private var context = LAContext() + private var reason: String + @Published private(set) var state = AuthenticationState.loggedOut + + init(reason: String) { + self.reason = reason + } + + func logOut() { + state = .loggedOut + } + + func canAuthenticate() -> Bool { + var error: NSError? + let canAuthenticate = LAContext().canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) + return canAuthenticate + } + + func authenticate(completion: ((AuthError?) -> Void)? = nil) { + + if state == .loggedIn { + completion?(nil) + return + } + + context = LAContext() + context.localizedCancelTitle = UserText.autofillLoginListAuthenticationCancelButton + context.localizedReason = reason + + if canAuthenticate() { + let reason = reason + context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason ) { success, error in + + DispatchQueue.main.async { + if success { + self.state = .loggedIn + completion?(nil) + } else { + os_log("Failed to authenticate: %s", log: .generalLog, type: .debug, error?.localizedDescription ?? "nil error") + completion?(.failedToAuthenticate) + } + } + } + } else { + state = .notAvailable + completion?(.noAuthAvailable) + } + } + + func invalidateContext() { + context.invalidate() + } +} diff --git a/DuckDuckGo/UserSession.swift b/DuckDuckGo/UserSession.swift new file mode 100644 index 0000000000..74c608bd20 --- /dev/null +++ b/DuckDuckGo/UserSession.swift @@ -0,0 +1,49 @@ +// +// UserSession.swift +// DuckDuckGo +// +// 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 + +class UserSession { + + private enum Constants { + static let defaultTimeout: TimeInterval = 15 + } + + private var sessionCreationDate: Date? + private let sessionTimeout: TimeInterval + + public init(sessionTimeout: TimeInterval = Constants.defaultTimeout) { + self.sessionTimeout = sessionTimeout + } + + public var isSessionValid: Bool { + guard let sessionCreationDate = sessionCreationDate else { return false } + let timeInterval = Date().timeIntervalSince(sessionCreationDate) + // Check that timeInterval is > 0 to prevent a user circumventing by changing their device clock time + return timeInterval > 0 && timeInterval < sessionTimeout + } + + public func startSession() { + sessionCreationDate = Date() + } + + public func endSession() { + sessionCreationDate = nil + } +} diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 8d36552646..5b9b476aac 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -861,6 +861,7 @@ But if you *do* want a peek under the hood, you can find more information about // MARK: Sync + public static let syncUserUserAuthenticationReason = NSLocalizedString("sync.user.auth.reason", value:"Unlock device to set up Sync & Backup", comment: "Reason for auth when setting up Sync") public static let syncTurnOffConfirmTitle = NSLocalizedString("sync.turn.off.confirm.title", value:"Turn Off Sync?", comment: "Title of the dialog to confirm turning off Sync") public static let syncTurnOffConfirmMessage = NSLocalizedString("sync.turn.off.confirm.message", value:"This Device will no longer be able to access your synced data.", comment: "Message for the dialog to confirm turning off Sync") public static let syncTurnOffConfirmAction = NSLocalizedString("sync.turn.off.confirm.action", value:"Remove", comment: "Caption for a button to remove current device from Sync") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 08268ce2dc..19241e7e48 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2085,6 +2085,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Title of the dialog to confirm turning off Sync */ "sync.turn.off.confirm.title" = "Turn Off Sync?"; +/* Reason for auth when setting up Sync */ +"sync.user.auth.reason" = "Unlock device to set up Sync & Backup"; + /* Data syncing unavailable warning message */ "sync.warning.data.syncing.disabled" = "Sorry, but Sync & Backup is currently unavailable. Please try again later."; diff --git a/DuckDuckGoTests/AutofillLoginSessionTests.swift b/DuckDuckGoTests/AutofillLoginSessionTests.swift index c7e35e346e..c3ed0733c8 100644 --- a/DuckDuckGoTests/AutofillLoginSessionTests.swift +++ b/DuckDuckGoTests/AutofillLoginSessionTests.swift @@ -27,18 +27,18 @@ final class AutofillLoginSessionTests: XCTestCase { private var autofillSession = AutofillLoginSession(sessionTimeout: 2) func testWhenThereIsNoSessionCreationDateThenAutofillSessionIsFalse() { - XCTAssertFalse(autofillSession.isValidSession) + XCTAssertFalse(autofillSession.isSessionValid) } func testWhenSessionStartedThenAutofillSessionIsValid() { autofillSession.startSession() - XCTAssertTrue(autofillSession.isValidSession) + XCTAssertTrue(autofillSession.isSessionValid) } func testWhenSessionEndedThenAutofillSessionIsInvalid() { autofillSession.startSession() autofillSession.endSession() - XCTAssertFalse(autofillSession.isValidSession) + XCTAssertFalse(autofillSession.isSessionValid) } func testWhenSessionExpiredThenAutofillSessionIsInvalid() { @@ -46,7 +46,7 @@ final class AutofillLoginSessionTests: XCTestCase { let sessionExpired = expectation(description: "testWhenSessionExpiredThenAutofillSessionIsInvalid") _ = XCTWaiter.wait(for: [sessionExpired], timeout: 2.2) - XCTAssertFalse(autofillSession.isValidSession) + XCTAssertFalse(autofillSession.isSessionValid) } func testWhenSessionIsValidAndAccountIsSetThenAccountIsReturned() { diff --git a/DuckDuckGoTests/SyncManagementViewModelTests.swift b/DuckDuckGoTests/SyncManagementViewModelTests.swift index 3fbf7ba3fa..d8735416cc 100644 --- a/DuckDuckGoTests/SyncManagementViewModelTests.swift +++ b/DuckDuckGoTests/SyncManagementViewModelTests.swift @@ -22,7 +22,7 @@ import XCTest /// To be fleshed out when UI is settled class SyncManagementViewModelTests: XCTestCase, SyncManagementViewModelDelegate { - + fileprivate var monitor = Monitor() lazy var model: SyncSettingsViewModel = { @@ -32,14 +32,14 @@ class SyncManagementViewModelTests: XCTestCase, SyncManagementViewModelDelegate }() var createAccountAndStartSyncingCalled = false - var caprturedOptionModel: SyncSettingsViewModel? + var capturedOptionModel: SyncSettingsViewModel? func testWhenSingleDeviceSetUpPressed_ThenManagerBecomesBusy_AndAccounCreationRequested() { model.startSyncPressed() XCTAssertTrue(model.isBusy) XCTAssertTrue(createAccountAndStartSyncingCalled) - XCTAssertNotNil(caprturedOptionModel) + XCTAssertNotNil(capturedOptionModel) } func testWhenShowRecoveryPDFPressed_ShowRecoveryPDFIsShown() { @@ -135,13 +135,17 @@ class SyncManagementViewModelTests: XCTestCase, SyncManagementViewModelDelegate } // MARK: Delegate functions + func authenticateUser() async -> Bool { + return true + } + func showSyncWithAnotherDeviceEnterText() { monitor.incrementCalls(function: #function.cleaningFunctionName()) } func createAccountAndStartSyncing(optionsViewModel: SyncSettingsViewModel) { createAccountAndStartSyncingCalled = true - caprturedOptionModel = optionsViewModel + capturedOptionModel = optionsViewModel } func showRecoverData() { diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift index a51474ebc3..93dd2b1df3 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift @@ -22,6 +22,7 @@ import UIKit public protocol SyncManagementViewModelDelegate: AnyObject { + func authenticateUser() async -> Bool func showRecoverData() func showSyncWithAnotherDevice() func showRecoveryPDF() @@ -102,6 +103,10 @@ public class SyncSettingsViewModel: ObservableObject { } } + func authenticateUser() async -> Bool { + await delegate?.authenticateUser() ?? false + } + func disableSync() { isBusy = true Task { @MainActor in diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift index 95922b61a5..bfec4047d5 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift @@ -75,7 +75,9 @@ extension SyncSettingsView { Section { Button(UserText.syncAndBackUpThisDeviceLink) { - isSyncWithSetUpSheetVisible = true + Task { @MainActor in + isSyncWithSetUpSheetVisible = await model.authenticateUser() + } } .sheet(isPresented: $isSyncWithSetUpSheetVisible, content: { SyncWithServerView(model: model, onCancel: { @@ -85,7 +87,9 @@ extension SyncSettingsView { .disabled(!model.isAccountCreationAvailable) Button(UserText.recoverSyncedDataLink) { - isRecoverSyncedDataSheetVisible = true + Task { @MainActor in + isRecoverSyncedDataSheetVisible = await model.authenticateUser() + } } .sheet(isPresented: $isRecoverSyncedDataSheetVisible, content: { RecoverSyncedDataView(model: model, onCancel: { From 6b294b193ba675fbfb9662cd765bd8006bcd1e3d Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Thu, 8 Feb 2024 10:59:23 +0100 Subject: [PATCH 017/245] Release 7.108.0-3 (#2454) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a4a08b9bcf..6f8e813b01 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8166,7 +8166,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8203,7 +8203,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8295,7 +8295,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8323,7 +8323,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8473,7 +8473,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8499,7 +8499,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8564,7 +8564,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8599,7 +8599,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8633,7 +8633,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8664,7 +8664,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8951,7 +8951,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8982,7 +8982,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9011,7 +9011,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9045,7 +9045,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9076,7 +9076,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9109,11 +9109,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9351,7 +9351,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9378,7 +9378,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9411,7 +9411,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9449,7 +9449,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9485,7 +9485,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9520,11 +9520,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9698,11 +9698,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9731,10 +9731,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 0e5519d7c973467a5a30560fdc63c9a2e0384b7f Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Thu, 8 Feb 2024 21:17:19 +0100 Subject: [PATCH 018/245] Point to BSK commit for macOS app moving prompt changes (#2441) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- submodules/privacy-reference-tests | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6f2e4d4e74..4d1df91a11 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9941,7 +9941,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 104.2.1; + version = 105.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 608fcb096b..7b5e6890b5 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "2555be729ff019465b4b263d9d68c2d10a88470a", - "version" : "104.2.1" + "revision" : "c14f36805fc5c4c8ec68198fc1ce7e0e0a97e233", + "version" : "105.0.0" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "38ee7284bac7fa12d822fcaf0677ea3969d15fb1", - "version" : "4.59.2" + "revision" : "063b560e59a50e03d9b00b88a7fcb2ed2b562395", + "version" : "4.61.0" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index d33c33c8bb..2c64017fc7 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.2.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "105.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 82e67f3956..e984890cbb 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.2.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "105.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index a70b7d8a56..2d43130bc5 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "104.2.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "105.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..a3acc21947 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit a3acc2194758bec0f01f57dd0c5f106de01a354e From 0513a1d00c4a92497424356ab6e98f1c84261a2b Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 9 Feb 2024 09:30:08 +0000 Subject: [PATCH 019/245] swipe tabs (#2370) --- Core/FeatureFlag.swift | 3 +- Core/PixelEvent.swift | 5 + Core/UIViewExtension.swift | 13 + DuckDuckGo.xcodeproj/project.pbxproj | 8 + DuckDuckGo/MainView.swift | 206 +++------- DuckDuckGo/MainViewController.swift | 142 +++++-- DuckDuckGo/MainViewCoordinator.swift | 147 +++++++ ...igationSearchHomeViewSectionRenderer.swift | 7 +- DuckDuckGo/OmniBar.swift | 18 +- DuckDuckGo/SwipeTabsCoordinator.swift | 359 ++++++++++++++++++ DuckDuckGo/WebViewTransition.swift | 1 - 11 files changed, 709 insertions(+), 200 deletions(-) create mode 100644 DuckDuckGo/MainViewCoordinator.swift create mode 100644 DuckDuckGo/SwipeTabsCoordinator.swift diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index e7e9b9165b..71bd3176ef 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -35,13 +35,14 @@ public enum FeatureFlag: String { case networkProtectionWaitlistAccess case networkProtectionWaitlistActive case subscription + case swipeTabs case autoconsentOnByDefault } extension FeatureFlag: FeatureFlagSourceProviding { public var source: FeatureFlagSource { switch self { - case .debugMenu, .appTrackingProtection, .subscription: + case .debugMenu, .appTrackingProtection, .subscription, .swipeTabs: return .internalOnly case .sync: return .remoteReleasable(.subfeature(SyncSubfeature.level0ShowSync)) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index a94d9a0693..49166d4662 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -499,6 +499,9 @@ extension Pixel { case syncDeleteAccountError case syncLoginExistingAccountError + case swipeTabsUsed + case swipeTabsUsedDaily + case bookmarksCleanupFailed case bookmarksCleanupAttemptedWhileSyncWasEnabled case favoritesCleanupFailed @@ -989,6 +992,8 @@ extension Pixel.Event { case .syncDeleteAccountError: return "m_d_sync_delete_account_error" case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error" + case .swipeTabsUsed: return "m_swipe-tabs-used" + case .swipeTabsUsedDaily: return "m_swipe-tabs-used-daily" case .bookmarksCleanupFailed: return "m_d_bookmarks_cleanup_failed" case .bookmarksCleanupAttemptedWhileSyncWasEnabled: return "m_d_bookmarks_cleanup_attempted_while_sync_was_enabled" diff --git a/Core/UIViewExtension.swift b/Core/UIViewExtension.swift index eb12e52945..d1cea20ad8 100644 --- a/Core/UIViewExtension.swift +++ b/Core/UIViewExtension.swift @@ -73,4 +73,17 @@ extension UIView { view.removeFromSuperview() } } + + @MainActor + public func createImageSnapshot(inBounds bounds: CGRect? = nil) -> UIImage? { + let bounds = bounds ?? self.frame + let size = bounds.size + UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale) + UIGraphicsGetCurrentContext()?.translateBy(x: -bounds.origin.x, y: -bounds.origin.y) + drawHierarchy(in: frame, afterScreenUpdates: true) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + return image + } + } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4d1df91a11..a3f3ca0348 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -459,6 +459,7 @@ 85AE668E2097206E0014CF04 /* NotificationView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 85AE668D2097206E0014CF04 /* NotificationView.xib */; }; 85AE6690209724120014CF04 /* NotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AE668F209724120014CF04 /* NotificationView.swift */; }; 85AFA1212B45D14F0028A504 /* BookmarksMigrationAssertionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AFA1202B45D14F0028A504 /* BookmarksMigrationAssertionTests.swift */; }; + 85B9814E2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B9814D2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift */; }; 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85B9CB8821AEBDD5009001F1 /* FavoriteHomeCell.swift */; }; 85BA58551F34F49E00C6E8CA /* AppUserDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BA58541F34F49E00C6E8CA /* AppUserDefaults.swift */; }; 85BA58581F34F72F00C6E8CA /* AppUserDefaultsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85BA58561F34F61C00C6E8CA /* AppUserDefaultsTests.swift */; }; @@ -491,6 +492,7 @@ 85DB12EB2A1FE2A4000A4A72 /* LockScreenWidgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DB12EA2A1FE2A4000A4A72 /* LockScreenWidgets.swift */; }; 85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DB12EC2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift */; }; 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DDE03F2AC6FF65006ABCA2 /* MainView.swift */; }; + 85DE681A2B6A8BB000DED4FE /* MainViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DE68192B6A8BB000DED4FE /* MainViewCoordinator.swift */; }; 85DF714624F7FE6100C89288 /* Core.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F143C2E41E4A4CD400CFDE3A /* Core.framework */; }; 85DFEDED24C7CCA500973FE7 /* AppWidthObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DFEDEC24C7CCA500973FE7 /* AppWidthObserver.swift */; }; 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DFEDEE24C7EA3B00973FE7 /* SmallOmniBarState.swift */; }; @@ -1547,6 +1549,7 @@ 85AE668D2097206E0014CF04 /* NotificationView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = NotificationView.xib; sourceTree = ""; }; 85AE668F209724120014CF04 /* NotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationView.swift; sourceTree = ""; }; 85AFA1202B45D14F0028A504 /* BookmarksMigrationAssertionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksMigrationAssertionTests.swift; sourceTree = ""; }; + 85B9814D2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeTabsCoordinator.swift; sourceTree = ""; }; 85B9CB8821AEBDD5009001F1 /* FavoriteHomeCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteHomeCell.swift; sourceTree = ""; }; 85BA58541F34F49E00C6E8CA /* AppUserDefaults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUserDefaults.swift; sourceTree = ""; }; 85BA58561F34F61C00C6E8CA /* AppUserDefaultsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppUserDefaultsTests.swift; sourceTree = ""; }; @@ -1580,6 +1583,7 @@ 85DB12EA2A1FE2A4000A4A72 /* LockScreenWidgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockScreenWidgets.swift; sourceTree = ""; }; 85DB12EC2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+AppDeepLinks.swift"; sourceTree = ""; }; 85DDE03F2AC6FF65006ABCA2 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; + 85DE68192B6A8BB000DED4FE /* MainViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewCoordinator.swift; sourceTree = ""; }; 85DFEDEC24C7CCA500973FE7 /* AppWidthObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppWidthObserver.swift; sourceTree = ""; }; 85DFEDEE24C7EA3B00973FE7 /* SmallOmniBarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmallOmniBarState.swift; sourceTree = ""; }; 85DFEDF024C7EEA400973FE7 /* LargeOmniBarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LargeOmniBarState.swift; sourceTree = ""; }; @@ -5396,6 +5400,8 @@ 85864FBB24D31EF300E756FF /* SuggestionTrayViewController.swift */, 851DFD86212C39D300D95F20 /* TabSwitcherButton.swift */, CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */, + 85B9814D2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift */, + 85DE68192B6A8BB000DED4FE /* MainViewCoordinator.swift */, ); name = Main; sourceTree = ""; @@ -6669,6 +6675,7 @@ 3151F0EE2735800800226F58 /* VoiceSearchFeedbackView.swift in Sources */, 857EEB752095FFAC008A005C /* HomeRowInstructionsViewController.swift in Sources */, 311BD1AF2836BB4200AEF6C1 /* AutofillItemsLockedView.swift in Sources */, + 85DE681A2B6A8BB000DED4FE /* MainViewCoordinator.swift in Sources */, 0290472A29E867800008FE3C /* AppTPTrackerDetailView.swift in Sources */, F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */, 85449EF523FDA02800512AAF /* KeyboardSettingsViewController.swift in Sources */, @@ -6894,6 +6901,7 @@ 3151F0EA27357FBA00226F58 /* SpeechRecognizer.swift in Sources */, F17922E21E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift in Sources */, 0290472229E723260008FE3C /* AppTPManageTrackerCell.swift in Sources */, + 85B9814E2B5EB618009AC9A6 /* SwipeTabsCoordinator.swift in Sources */, 985AAE4524899369007A43EC /* HomeScreenTransition.swift in Sources */, 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */, 1E8AD1CF27C000A000ABA377 /* CompleteDownloadRow.swift in Sources */, diff --git a/DuckDuckGo/MainView.swift b/DuckDuckGo/MainView.swift index 4d5171167e..06564f8329 100644 --- a/DuckDuckGo/MainView.swift +++ b/DuckDuckGo/MainView.swift @@ -19,9 +19,6 @@ import UIKit -// swiftlint:disable file_length -// swiftlint:disable line_length - class MainViewFactory { private let coordinator: MainViewCoordinator @@ -60,9 +57,11 @@ extension MainViewFactory { createNotificationBarContainer() createStatusBackground() createTabBarContainer() + createOmniBar() + createToolbar() createNavigationBarContainer() + createNavigationBarCollectionView() createProgressView() - createToolbar() } private func createProgressView() { @@ -70,12 +69,43 @@ extension MainViewFactory { superview.addSubview(coordinator.progress) } - final class NavigationBarContainer: UIView { } - private func createNavigationBarContainer() { + private func createOmniBar() { coordinator.omniBar = OmniBar.loadFromXib() coordinator.omniBar.translatesAutoresizingMaskIntoConstraints = false + } + + final class NavigationBarCollectionView: UICollectionView { + + var hitTestInsets = UIEdgeInsets.zero + + override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return bounds.inset(by: hitTestInsets).contains(point) + } + + // Don't allow the use to drag the scrollbar or the UI will glitch. + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let view = super.hitTest(point, with: event) + if view == self.subviews.first(where: { $0 is UIImageView }) { + return nil + } + return view + } + } + + private func createNavigationBarCollectionView() { + // Layout is replaced elsewhere, but required to construct the view. + coordinator.navigationBarCollectionView = NavigationBarCollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + + // scrollview subclasses change the default to true, but we need this for the separator on the omnibar + coordinator.navigationBarCollectionView.clipsToBounds = false + + coordinator.navigationBarCollectionView.translatesAutoresizingMaskIntoConstraints = false + coordinator.navigationBarContainer.addSubview(coordinator.navigationBarCollectionView) + } + + final class NavigationBarContainer: UIView { } + private func createNavigationBarContainer() { coordinator.navigationBarContainer = NavigationBarContainer() - coordinator.navigationBarContainer.addSubview(coordinator.omniBar) superview.addSubview(coordinator.navigationBarContainer) } @@ -181,26 +211,27 @@ extension MainViewFactory { coordinator.constraints.progressBarTop, ]) } - + private func constrainNavigationBarContainer() { let navigationBarContainer = coordinator.navigationBarContainer! let toolbar = coordinator.toolbar! - let omniBar = coordinator.omniBar! + let navigationBarCollectionView = coordinator.navigationBarCollectionView! coordinator.constraints.navigationBarContainerTop = navigationBarContainer.constrainView(superview.safeAreaLayoutGuide, by: .top) coordinator.constraints.navigationBarContainerBottom = navigationBarContainer.constrainView(toolbar, by: .bottom, to: .top) - coordinator.constraints.omniBarBottom = omniBar.constrainView(navigationBarContainer, by: .bottom, relatedBy: .greaterThanOrEqual) - + coordinator.constraints.navigationBarCollectionViewBottom + = navigationBarCollectionView.constrainView(navigationBarContainer, by: .bottom, relatedBy: .greaterThanOrEqual) + NSLayoutConstraint.activate([ coordinator.constraints.navigationBarContainerTop, - navigationBarContainer.constrainView(superview, by: .centerX), - navigationBarContainer.constrainView(superview, by: .width), + navigationBarContainer.constrainView(superview, by: .leading), + navigationBarContainer.constrainView(superview, by: .trailing), navigationBarContainer.constrainAttribute(.height, to: 52, relatedBy: .greaterThanOrEqual), - omniBar.constrainAttribute(.height, to: 52), - omniBar.constrainView(navigationBarContainer, by: .top), - omniBar.constrainView(navigationBarContainer, by: .leading), - omniBar.constrainView(navigationBarContainer, by: .trailing), - coordinator.constraints.omniBarBottom, + navigationBarCollectionView.constrainAttribute(.height, to: 52), + navigationBarCollectionView.constrainView(navigationBarContainer, by: .top), + navigationBarCollectionView.constrainView(navigationBarContainer, by: .leading), + navigationBarCollectionView.constrainView(navigationBarContainer, by: .trailing), + coordinator.constraints.navigationBarCollectionViewBottom ]) } @@ -221,9 +252,11 @@ extension MainViewFactory { let statusBackground = coordinator.statusBackground! let navigationBarContainer = coordinator.navigationBarContainer! - coordinator.constraints.statusBackgroundToNavigationBarContainerBottom = statusBackground.constrainView(navigationBarContainer, by: .bottom) + coordinator.constraints.statusBackgroundToNavigationBarContainerBottom + = statusBackground.constrainView(navigationBarContainer, by: .bottom) - coordinator.constraints.statusBackgroundBottomToSafeAreaTop = statusBackground.constrainView(coordinator.superview.safeAreaLayoutGuide, by: .bottom, to: .top) + coordinator.constraints.statusBackgroundBottomToSafeAreaTop + = statusBackground.constrainView(coordinator.superview.safeAreaLayoutGuide, by: .bottom, to: .top) NSLayoutConstraint.activate([ statusBackground.constrainView(superview, by: .width), @@ -239,8 +272,10 @@ extension MainViewFactory { let navigationBarContainer = coordinator.navigationBarContainer! let statusBackground = coordinator.statusBackground! - coordinator.constraints.notificationContainerTopToNavigationBar = notificationBarContainer.constrainView(navigationBarContainer, by: .top, to: .bottom) - coordinator.constraints.notificationContainerTopToStatusBackground = notificationBarContainer.constrainView(statusBackground, by: .top, to: .bottom) + coordinator.constraints.notificationContainerTopToNavigationBar + = notificationBarContainer.constrainView(navigationBarContainer, by: .top, to: .bottom) + coordinator.constraints.notificationContainerTopToStatusBackground + = notificationBarContainer.constrainView(statusBackground, by: .top, to: .bottom) coordinator.constraints.notificationContainerHeight = notificationBarContainer.constrainAttribute(.height, to: 0) NSLayoutConstraint.activate([ @@ -260,7 +295,8 @@ extension MainViewFactory { coordinator.constraints.contentContainerTop = contentContainer.constrainView(notificationBarContainer, by: .top, to: .bottom) coordinator.constraints.contentContainerBottomToToolbarTop = contentContainer.constrainView(toolbar, by: .bottom, to: .top) - coordinator.constraints.contentContainerBottomToNavigationBarContainerTop = contentContainer.constrainView(navigationBarContainer, by: .bottom, to: .top) + coordinator.constraints.contentContainerBottomToNavigationBarContainerTop + = contentContainer.constrainView(navigationBarContainer, by: .bottom, to: .top) NSLayoutConstraint.activate([ contentContainer.constrainView(superview, by: .leading), @@ -311,127 +347,3 @@ extension MainViewFactory { } } - -class MainViewCoordinator { - - let superview: UIView - - var contentContainer: UIView! - var lastToolbarButton: UIBarButtonItem! - var logo: UIImageView! - var logoContainer: UIView! - var logoText: UIImageView! - var navigationBarContainer: UIView! - var notificationBarContainer: UIView! - var omniBar: OmniBar! - var progress: ProgressView! - var statusBackground: UIView! - var suggestionTrayContainer: UIView! - var tabBarContainer: UIView! - var toolbar: UIToolbar! - var toolbarBackButton: UIBarButtonItem! - var toolbarFireButton: UIBarButtonItem! - var toolbarForwardButton: UIBarButtonItem! - var toolbarTabSwitcherButton: UIBarButtonItem! - - let constraints = Constraints() - - // The default after creating the hiearchy is top - var addressBarPosition: AddressBarPosition = .top - - fileprivate init(superview: UIView) { - self.superview = superview - } - - func decorateWithTheme(_ theme: Theme) { - superview.backgroundColor = theme.mainViewBackgroundColor - logoText.tintColor = theme.ddgTextTintColor - omniBar.decorate(with: theme) - } - - func showToolbarSeparator() { - toolbar.setShadowImage(nil, forToolbarPosition: .any) - } - - func hideToolbarSeparator() { - self.toolbar.setShadowImage(UIImage(), forToolbarPosition: .any) - } - - class Constraints { - - var navigationBarContainerTop: NSLayoutConstraint! - var navigationBarContainerBottom: NSLayoutConstraint! - var toolbarBottom: NSLayoutConstraint! - var contentContainerTop: NSLayoutConstraint! - var tabBarContainerTop: NSLayoutConstraint! - var notificationContainerTopToNavigationBar: NSLayoutConstraint! - var notificationContainerTopToStatusBackground: NSLayoutConstraint! - var notificationContainerHeight: NSLayoutConstraint! - var omniBarBottom: NSLayoutConstraint! - var progressBarTop: NSLayoutConstraint! - var progressBarBottom: NSLayoutConstraint! - var statusBackgroundToNavigationBarContainerBottom: NSLayoutConstraint! - var statusBackgroundBottomToSafeAreaTop: NSLayoutConstraint! - var contentContainerBottomToToolbarTop: NSLayoutConstraint! - var contentContainerBottomToNavigationBarContainerTop: NSLayoutConstraint! - - } - - func moveAddressBarToPosition(_ position: AddressBarPosition) { - guard position != addressBarPosition else { return } - switch position { - case .top: - setAddressBarBottomActive(false) - setAddressBarTopActive(true) - - case .bottom: - setAddressBarTopActive(false) - setAddressBarBottomActive(true) - } - - addressBarPosition = position - } - - func hideNavigationBarWithBottomPosition() { - guard addressBarPosition.isBottom else { - return - } - - // Hiding the container won't suffice as it still defines the contentContainer.bottomY through constraints - navigationBarContainer.isHidden = true - - constraints.contentContainerBottomToNavigationBarContainerTop.isActive = false - constraints.contentContainerBottomToToolbarTop.isActive = true - } - - func showNavigationBarWithBottomPosition() { - guard addressBarPosition.isBottom else { - return - } - - constraints.contentContainerBottomToToolbarTop.isActive = false - constraints.contentContainerBottomToNavigationBarContainerTop.isActive = true - - navigationBarContainer.isHidden = false - } - - func setAddressBarTopActive(_ active: Bool) { - constraints.contentContainerBottomToToolbarTop.isActive = active - constraints.navigationBarContainerTop.isActive = active - constraints.progressBarTop.isActive = active - constraints.notificationContainerTopToNavigationBar.isActive = active - constraints.statusBackgroundToNavigationBarContainerBottom.isActive = active - } - - func setAddressBarBottomActive(_ active: Bool) { - constraints.contentContainerBottomToNavigationBarContainerTop.isActive = active - constraints.progressBarBottom.isActive = active - constraints.navigationBarContainerBottom.isActive = active - constraints.notificationContainerTopToStatusBackground.isActive = active - constraints.statusBackgroundBottomToSafeAreaTop.isActive = active - } - -} - -// swiftlint:enable line_length -// swiftlint:enable file_length diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index c9e0cafaaf..697640fce6 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -38,8 +38,8 @@ import NetworkProtection // swiftlint:disable file_length // swiftlint:disable type_body_length class MainViewController: UIViewController { -// swiftlint:enable type_body_length - + // swiftlint:enable type_body_length + override var preferredStatusBarStyle: UIStatusBarStyle { return ThemeManager.shared.currentTheme.statusBarStyle } @@ -49,15 +49,15 @@ class MainViewController: UIViewController { return isIPad ? [.left, .right] : [] } - + weak var findInPageView: FindInPageView! weak var findInPageHeightLayoutConstraint: NSLayoutConstraint! weak var findInPageBottomLayoutConstraint: NSLayoutConstraint! - + weak var notificationView: NotificationView? - + var chromeManager: BrowserChromeManager! - + var allowContentUnderflow = false { didSet { viewCoordinator.constraints.contentContainerTop.constant = allowContentUnderflow ? contentUnderflow : 0 @@ -67,36 +67,36 @@ class MainViewController: UIViewController { var contentUnderflow: CGFloat { return 3 + (allowContentUnderflow ? -viewCoordinator.navigationBarContainer.frame.size.height : 0) } - + lazy var emailManager: EmailManager = { let emailManager = EmailManager() emailManager.aliasPermissionDelegate = self emailManager.requestDelegate = self return emailManager }() - + var homeController: HomeViewController? var tabsBarController: TabsBarViewController? var suggestionTrayController: SuggestionTrayViewController? - + var tabManager: TabManager! let previewsSource = TabPreviewsSource() let appSettings: AppSettings private var launchTabObserver: LaunchTabNotification.Observer? - + #if APP_TRACKING_PROTECTION private let appTrackingProtectionDatabase: CoreDataDatabase #endif - + let bookmarksDatabase: CoreDataDatabase private weak var bookmarksDatabaseCleaner: BookmarkDatabaseCleaner? private var favoritesViewModel: FavoritesListInteracting let syncService: DDGSyncing let syncDataProviders: SyncDataProviders - + @UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false) private var syncDidShowSyncPausedByFeatureFlagAlert: Bool - + private var localUpdatesCancellable: AnyCancellable? private var syncUpdatesCancellable: AnyCancellable? private var syncFeatureFlagsCancellable: AnyCancellable? @@ -108,13 +108,13 @@ class MainViewController: UIViewController { #endif private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger - + lazy var menuBookmarksViewModel: MenuBookmarksInteracting = { let viewModel = MenuBookmarksViewModel(bookmarksDatabase: bookmarksDatabase, syncService: syncService) viewModel.favoritesDisplayMode = appSettings.favoritesDisplayMode return viewModel }() - + weak var tabSwitcherController: TabSwitcherViewController? let tabSwitcherButton = TabSwitcherButton() @@ -129,23 +129,23 @@ class MainViewController: UIViewController { private lazy var fireButtonAnimator: FireButtonAnimator = FireButtonAnimator(appSettings: appSettings) let bookmarksCachingSearch: BookmarksCachingSearch - + lazy var tabSwitcherTransition = TabSwitcherTransitionDelegate() var currentTab: TabViewController? { return tabManager?.current(createIfNeeded: false) } - + var searchBarRect: CGRect { let view = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first?.rootViewController?.view return viewCoordinator.omniBar.searchContainer.convert(viewCoordinator.omniBar.searchContainer.bounds, to: view) } - + var keyModifierFlags: UIKeyModifierFlags? var showKeyboardAfterFireButton: DispatchWorkItem? // Skip SERP flow (focusing on autocomplete logic) and prepare for new navigation when selecting search bar private var skipSERPFlow = true - + private var keyboardHeight: CGFloat = 0.0 var postClear: (() -> Void)? @@ -154,9 +154,9 @@ class MainViewController: UIViewController { required init?(coder: NSCoder) { fatalError("Use init?(code:") } - + var viewCoordinator: MainViewCoordinator! - + #if APP_TRACKING_PROTECTION init( bookmarksDatabase: CoreDataDatabase, @@ -174,9 +174,9 @@ class MainViewController: UIViewController { self.favoritesViewModel = FavoritesListViewModel(bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: appSettings.favoritesDisplayMode) self.bookmarksCachingSearch = BookmarksCachingSearch(bookmarksStore: CoreDataBookmarksSearchStore(bookmarksStore: bookmarksDatabase)) self.appSettings = appSettings - + super.init(nibName: nil, bundle: nil) - + bindFavoritesDisplayMode() bindSyncService() } @@ -197,35 +197,37 @@ class MainViewController: UIViewController { self.appSettings = appSettings super.init(nibName: nil, bundle: nil) - + bindSyncService() } #endif - + fileprivate var tabCountInfo: TabCountInfo? - + func loadFindInPage() { - + let view = FindInPageView.loadFromXib() self.view.addSubview(view) - + // Avoids coercion swiftlint warnings let superview = self.view! - + let height = view.constrainAttribute(.height, to: view.frame.height) let bottom = superview.constrainView(view, by: .bottom, to: .bottom) - + NSLayoutConstraint.activate([ bottom, superview.constrainView(view, by: .width, to: .width), height, superview.constrainView(view, by: .centerX, to: .centerX) ]) - + findInPageView = view findInPageBottomLayoutConstraint = bottom findInPageHeightLayoutConstraint = height } + + var swipeTabsCoordinator: SwipeTabsCoordinator? override func viewDidLoad() { super.viewDidLoad() @@ -237,6 +239,8 @@ class MainViewController: UIViewController { viewCoordinator.toolbarForwardButton.action = #selector(onForwardPressed) viewCoordinator.toolbarFireButton.action = #selector(onFirePressed) + installSwipeTabs() + loadSuggestionTray() loadTabsBarIfNeeded() loadFindInPage() @@ -267,6 +271,7 @@ class MainViewController: UIViewController { applyTheme(ThemeManager.shared.currentTheme) tabsBarController?.refresh(tabsModel: tabManager.model, scrollToSelected: true) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) _ = AppWidthObserver.shared.willResize(toWidth: view.frame.width) applyWidth() @@ -277,14 +282,19 @@ class MainViewController: UIViewController { tabManager.cleanupTabsFaviconCache() + // Needs to be called here to established correct view hierarchy refreshViewsBasedOnAddressBarPosition(appSettings.currentAddressBarPosition) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // Needs to be called here because sometimes the frames are not the expected size during didLoad + refreshViewsBasedOnAddressBarPosition(appSettings.currentAddressBarPosition) + startOnboardingFlowIfNotSeenBefore() tabsBarController?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) _ = AppWidthObserver.shared.willResize(toWidth: view.frame.width) applyWidth() @@ -298,6 +308,53 @@ class MainViewController: UIViewController { assertionFailure() super.performSegue(withIdentifier: identifier, sender: sender) } + + private func installSwipeTabs() { + guard swipeTabsCoordinator == nil else { return } + + swipeTabsCoordinator = SwipeTabsCoordinator(coordinator: viewCoordinator, + tabPreviewsSource: previewsSource, + appSettings: appSettings) { [weak self] in + + guard $0 != self?.tabManager.model.currentIndex else { return } + + DailyPixel.fire(pixel: .swipeTabsUsedDaily) + Pixel.fire(pixel: .swipeTabsUsed) + self?.select(tabAt: $0) + + } newTab: { [weak self] in + self?.newTab() + } onSwipeStarted: { [weak self] in + self?.hideKeyboard() + self?.updatePreviewForCurrentTab() + } + } + + func updatePreviewForCurrentTab() { + assert(Thread.isMainThread) + + if !viewCoordinator.logoContainer.isHidden, + self.tabManager.current()?.link == nil, + let tab = self.tabManager.model.currentTab { + // Home screen with logo + if let image = viewCoordinator.logoContainer.createImageSnapshot(inBounds: viewCoordinator.contentContainer.frame) { + previewsSource.update(preview: image, forTab: tab) + } + + } else if let currentTab = self.tabManager.current(), currentTab.link != nil { + // Web view + currentTab.preparePreview(completion: { image in + guard let image else { return } + self.previewsSource.update(preview: image, + forTab: currentTab.tabModel) + }) + } else if let tab = self.tabManager.model.currentTab { + // Favorites, etc + if let image = viewCoordinator.contentContainer.createImageSnapshot() { + previewsSource.update(preview: image, forTab: tab) + } + } + } func loadSuggestionTray() { let storyboard = UIStoryboard(name: "SuggestionTray", bundle: nil) @@ -452,10 +509,13 @@ class MainViewController: UIViewController { func refreshViewsBasedOnAddressBarPosition(_ position: AddressBarPosition) { switch position { case .top: + swipeTabsCoordinator?.addressBarPositionChanged(isTop: true) viewCoordinator.omniBar.moveSeparatorToBottom() viewCoordinator.showToolbarSeparator() + viewCoordinator.constraints.navigationBarContainerBottom.isActive = false case .bottom: + swipeTabsCoordinator?.addressBarPositionChanged(isTop: false) viewCoordinator.omniBar.moveSeparatorToTop() // If this is called before the toolbar has shown it will not re-add the separator when moving to the top position DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { @@ -513,7 +573,7 @@ class MainViewController: UIViewController { if self.appSettings.currentAddressBarPosition.isBottom { let navBarOffset = min(0, self.toolbarHeight - intersection.height) - self.viewCoordinator.constraints.omniBarBottom.constant = navBarOffset + self.viewCoordinator.constraints.navigationBarCollectionViewBottom.constant = navBarOffset UIView.animate(withDuration: duration, delay: 0, options: animationCurve) { self.viewCoordinator.navigationBarContainer.superview?.layoutIfNeeded() } @@ -608,7 +668,7 @@ class MainViewController: UIViewController { override var shouldAutorotate: Bool { return true } - + @objc func dismissSuggestionTray() { dismissOmniBar() } @@ -805,6 +865,7 @@ class MainViewController: UIViewController { refreshTabIcon() refreshControls() tabsBarController?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) } if clearInProgress { @@ -894,6 +955,7 @@ class MainViewController: UIViewController { refreshControls() } tabsBarController?.refresh(tabsModel: tabManager.model, scrollToSelected: true) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) if DaxDialogs.shared.shouldShowFireButtonPulse { showFireButtonPulse() } @@ -989,7 +1051,9 @@ class MainViewController: UIViewController { self.showMenuHighlighterIfNeeded() - coordinator.animate(alongsideTransition: nil) { _ in + coordinator.animate { _ in + self.swipeTabsCoordinator?.refresh(tabsModel: self.tabManager.model, scrollToSelected: true) + } completion: { _ in ViewHighlighter.updatePositions() } } @@ -1011,6 +1075,7 @@ class MainViewController: UIViewController { } // If tabs have been udpated, do this async to make sure size calcs are current self.tabsBarController?.refresh(tabsModel: self.tabManager.model) + self.swipeTabsCoordinator?.refresh(tabsModel: self.tabManager.model) // Do this on the next UI thread pass so we definitely have the right width self.applyWidthToTrayController() @@ -1052,12 +1117,16 @@ class MainViewController: UIViewController { viewCoordinator.tabBarContainer.isHidden = false viewCoordinator.toolbar.isHidden = true viewCoordinator.omniBar.enterPadState() + + swipeTabsCoordinator?.isEnabled = false } private func applySmallWidth() { viewCoordinator.tabBarContainer.isHidden = true viewCoordinator.toolbar.isHidden = false viewCoordinator.omniBar.enterPhoneState() + + swipeTabsCoordinator?.isEnabled = featureFlagger.isFeatureOn(.swipeTabs) } @discardableResult @@ -1190,8 +1259,9 @@ class MainViewController: UIViewController { tabManager.addHomeTab() } attachHomeScreen() - homeController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) tabsBarController?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) + homeController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) } func animateLogoAppearance() { @@ -1792,6 +1862,7 @@ extension MainViewController: TabDelegate { } tabManager?.save() tabsBarController?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) } func tab(_ tab: TabViewController, didUpdatePreview preview: UIImage) { @@ -2091,6 +2162,7 @@ extension MainViewController: AutoClearWorker { showBars() attachHomeScreen() tabsBarController?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) Favicons.shared.clearCache(.tabs) } diff --git a/DuckDuckGo/MainViewCoordinator.swift b/DuckDuckGo/MainViewCoordinator.swift new file mode 100644 index 0000000000..9906ef7767 --- /dev/null +++ b/DuckDuckGo/MainViewCoordinator.swift @@ -0,0 +1,147 @@ +// +// MainViewCoordinator.swift +// DuckDuckGo +// +// 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 UIKit + +class MainViewCoordinator { + + let superview: UIView + + var contentContainer: UIView! + var lastToolbarButton: UIBarButtonItem! + var logo: UIImageView! + var logoContainer: UIView! + var logoText: UIImageView! + var navigationBarContainer: UIView! + var navigationBarCollectionView: MainViewFactory.NavigationBarCollectionView! + var notificationBarContainer: UIView! + var omniBar: OmniBar! + var progress: ProgressView! + var statusBackground: UIView! + var suggestionTrayContainer: UIView! + var tabBarContainer: UIView! + var toolbar: UIToolbar! + var toolbarBackButton: UIBarButtonItem! + var toolbarFireButton: UIBarButtonItem! + var toolbarForwardButton: UIBarButtonItem! + var toolbarTabSwitcherButton: UIBarButtonItem! + + let constraints = Constraints() + + // The default after creating the hiearchy is top + var addressBarPosition: AddressBarPosition = .top + + /// STOP - why are you instanciating this? + init(superview: UIView) { + self.superview = superview + } + + func showToolbarSeparator() { + toolbar.setShadowImage(nil, forToolbarPosition: .any) + } + + func hideToolbarSeparator() { + self.toolbar.setShadowImage(UIImage(), forToolbarPosition: .any) + } + + class Constraints { + + var navigationBarContainerTop: NSLayoutConstraint! + var navigationBarContainerBottom: NSLayoutConstraint! + var navigationBarCollectionViewBottom: NSLayoutConstraint! + var toolbarBottom: NSLayoutConstraint! + var contentContainerTop: NSLayoutConstraint! + var tabBarContainerTop: NSLayoutConstraint! + var notificationContainerTopToNavigationBar: NSLayoutConstraint! + var notificationContainerTopToStatusBackground: NSLayoutConstraint! + var notificationContainerHeight: NSLayoutConstraint! + var progressBarTop: NSLayoutConstraint! + var progressBarBottom: NSLayoutConstraint! + var statusBackgroundToNavigationBarContainerBottom: NSLayoutConstraint! + var statusBackgroundBottomToSafeAreaTop: NSLayoutConstraint! + var contentContainerBottomToToolbarTop: NSLayoutConstraint! + var contentContainerBottomToNavigationBarContainerTop: NSLayoutConstraint! + + } + + func moveAddressBarToPosition(_ position: AddressBarPosition) { + guard position != addressBarPosition else { return } + switch position { + case .top: + setAddressBarBottomActive(false) + setAddressBarTopActive(true) + + case .bottom: + setAddressBarTopActive(false) + setAddressBarBottomActive(true) + } + + addressBarPosition = position + } + + func hideNavigationBarWithBottomPosition() { + guard addressBarPosition.isBottom else { + return + } + + // Hiding the container won't suffice as it still defines the contentContainer.bottomY through constraints + navigationBarContainer.isHidden = true + + constraints.contentContainerBottomToNavigationBarContainerTop.isActive = false + constraints.contentContainerBottomToToolbarTop.isActive = true + } + + func showNavigationBarWithBottomPosition() { + guard addressBarPosition.isBottom else { + return + } + + constraints.contentContainerBottomToToolbarTop.isActive = false + constraints.contentContainerBottomToNavigationBarContainerTop.isActive = true + + navigationBarContainer.isHidden = false + } + + func setAddressBarTopActive(_ active: Bool) { + constraints.contentContainerBottomToToolbarTop.isActive = active + constraints.navigationBarContainerTop.isActive = active + constraints.progressBarTop.isActive = active + constraints.notificationContainerTopToNavigationBar.isActive = active + constraints.statusBackgroundToNavigationBarContainerBottom.isActive = active + } + + func setAddressBarBottomActive(_ active: Bool) { + constraints.contentContainerBottomToNavigationBarContainerTop.isActive = active + constraints.progressBarBottom.isActive = active + constraints.navigationBarContainerBottom.isActive = active + constraints.notificationContainerTopToStatusBackground.isActive = active + constraints.statusBackgroundBottomToSafeAreaTop.isActive = active + } + +} + +extension MainViewCoordinator: Themable { + + func decorate(with theme: Theme) { + superview.backgroundColor = theme.mainViewBackgroundColor + logoText.tintColor = theme.ddgTextTintColor + omniBar.decorate(with: theme) + } + +} diff --git a/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift b/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift index 5830de7157..34f23fb686 100644 --- a/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift +++ b/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift @@ -46,8 +46,11 @@ class NavigationSearchHomeViewSectionRenderer: HomeViewSectionRenderer { } func openedAsNewTab(allowingKeyboard: Bool) { - if allowingKeyboard && KeyboardSettings().onNewTab { - launchNewSearch() + guard allowingKeyboard && KeyboardSettings().onNewTab else { return } + // The omnibar is inside a collection view so this needs to chance to do its thing + // which might also be async. Not great. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.launchNewSearch() } } diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index 4a3bc54fd0..282bd81f81 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -66,7 +66,6 @@ class OmniBar: UIView { weak var omniDelegate: OmniBarDelegate? fileprivate var state: OmniBarState = SmallOmniBarState.HomeNonEditingState() - private var safeAreaInsetsObservation: NSKeyValueObservation? private var privacyIconAndTrackersAnimator = PrivacyIconAndTrackersAnimator() private var notificationAnimator = OmniBarNotificationAnimator() @@ -100,7 +99,6 @@ class OmniBar: UIView { configureEditingMenu() refreshState(state) enableInteractionsWithPointer() - observeSafeAreaInsets() privacyInfoContainer.isHidden = true } @@ -140,13 +138,7 @@ class OmniBar: UIView { name: .speechRecognizerDidChangeAvailability, object: nil) } - - private func observeSafeAreaInsets() { - safeAreaInsetsObservation = self.observe(\.safeAreaInsets, options: .new) { [weak self] (_, _) in - self?.updateOmniBarPadding() - } - } - + private func enableInteractionsWithPointer() { backButton.isPointerInteractionEnabled = true forwardButton.isPointerInteractionEnabled = true @@ -346,8 +338,6 @@ class OmniBar: UIView { if state.showVoiceSearch && state.showClear { searchStackContainer.setCustomSpacing(13, after: voiceSearchButton) } - - updateOmniBarPadding() UIView.animate(withDuration: 0.0) { self.layoutIfNeeded() @@ -355,9 +345,9 @@ class OmniBar: UIView { } - private func updateOmniBarPadding() { - omniBarLeadingConstraint.constant = (state.hasLargeWidth ? 24 : 8) + safeAreaInsets.left - omniBarTrailingConstraint.constant = (state.hasLargeWidth ? 24 : 14) + safeAreaInsets.right + func updateOmniBarPadding(left: CGFloat, right: CGFloat) { + omniBarLeadingConstraint.constant = (state.hasLargeWidth ? 24 : 8) + left + omniBarTrailingConstraint.constant = (state.hasLargeWidth ? 24 : 14) + right } /* diff --git a/DuckDuckGo/SwipeTabsCoordinator.swift b/DuckDuckGo/SwipeTabsCoordinator.swift new file mode 100644 index 0000000000..180d9a17a0 --- /dev/null +++ b/DuckDuckGo/SwipeTabsCoordinator.swift @@ -0,0 +1,359 @@ +// +// SwipeTabsCoordinator.swift +// DuckDuckGo +// +// 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 UIKit + +class SwipeTabsCoordinator: NSObject { + + static let tabGap: CGFloat = 10 + + // Set by refresh function + weak var tabsModel: TabsModel! + + weak var coordinator: MainViewCoordinator! + weak var tabPreviewsSource: TabPreviewsSource! + weak var appSettings: AppSettings! + + let selectTab: (Int) -> Void + let newTab: () -> Void + let onSwipeStarted: () -> Void + + let feedbackGenerator: UISelectionFeedbackGenerator = { + let generator = UISelectionFeedbackGenerator() + generator.prepare() + return generator + }() + + var isEnabled = false { + didSet { + collectionView.reloadData() + } + } + + var collectionView: MainViewFactory.NavigationBarCollectionView { + coordinator.navigationBarCollectionView + } + + init(coordinator: MainViewCoordinator, + tabPreviewsSource: TabPreviewsSource, + appSettings: AppSettings, + selectTab: @escaping (Int) -> Void, + newTab: @escaping () -> Void, + onSwipeStarted: @escaping () -> Void) { + + self.coordinator = coordinator + self.tabPreviewsSource = tabPreviewsSource + self.appSettings = appSettings + + self.selectTab = selectTab + self.newTab = newTab + self.onSwipeStarted = onSwipeStarted + + super.init() + + collectionView.register(OmniBarCell.self, forCellWithReuseIdentifier: "omnibar") + collectionView.isPagingEnabled = true + collectionView.delegate = self + collectionView.dataSource = self + collectionView.decelerationRate = .fast + collectionView.backgroundColor = .clear + + updateLayout() + } + + enum State { + + case idle + case starting(CGPoint) + case swiping(CGPoint, FloatingPointSign) + + } + + var state: State = .idle + + weak var preview: UIView? + weak var currentView: UIView? + + private func updateLayout() { + let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout + layout?.scrollDirection = .horizontal + layout?.itemSize = CGSize(width: coordinator.superview.frame.size.width, height: coordinator.omniBar.frame.height) + layout?.minimumLineSpacing = 0 + layout?.minimumInteritemSpacing = 0 + layout?.scrollDirection = .horizontal + } + + private func scrollToCurrent() { + guard isEnabled else { return } + + let targetOffset = collectionView.frame.width * CGFloat(tabsModel.currentIndex) + + guard targetOffset != collectionView.contentOffset.x else { + return + } + + let indexPath = IndexPath(row: self.tabsModel.currentIndex, section: 0) + self.collectionView.scrollToItem(at: indexPath, + at: .centeredHorizontally, + animated: false) + } +} + +// MARK: UICollectionViewDelegate +extension SwipeTabsCoordinator: UICollectionViewDelegate { + + func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + DispatchQueue.main.async { + self.scrollToCurrent() + } + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + switch state { + case .idle: break + + case .starting(let startPosition): + let offset = startPosition.x - scrollView.contentOffset.x + prepareCurrentView() + preparePreview(offset) + state = .swiping(startPosition, offset.sign) + onSwipeStarted() + + case .swiping(let startPosition, let sign): + let offset = startPosition.x - scrollView.contentOffset.x + if offset.sign == sign { + let modifier = sign == .plus ? -1.0 : 1.0 + swipePreviewProportionally(offset: offset, modifier: modifier) + swipeCurrentViewProportionally(offset: offset) + currentView?.transform.tx = offset + } else { + cleanUpViews() + state = .starting(startPosition) + } + } + } + + private func swipeCurrentViewProportionally(offset: CGFloat) { + currentView?.transform.tx = offset + } + + private func swipePreviewProportionally(offset: CGFloat, modifier: CGFloat) { + let width = coordinator.contentContainer.frame.width + let percent = offset / width + let swipeWidth = width + Self.tabGap + let x = (swipeWidth * percent) + (Self.tabGap * modifier) + preview?.transform.tx = x + } + + private func prepareCurrentView() { + + if !coordinator.logoContainer.isHidden { + currentView = coordinator.logoContainer + } else { + currentView = coordinator.contentContainer.subviews.last + } + } + + private func preparePreview(_ offset: CGFloat) { + let modifier = (offset > 0 ? -1 : 1) + let nextIndex = tabsModel.currentIndex + modifier + + guard tabsModel.tabs.indices.contains(nextIndex) || tabsModel.tabs.last?.link != nil else { + return + } + + let targetFrame = CGRect(origin: .zero, size: coordinator.contentContainer.frame.size) + + let tab = tabsModel.safeGetTabAt(nextIndex) + if let tab, let image = tabPreviewsSource.preview(for: tab) { + createPreviewFromImage(image) + } else if tab?.link == nil { + createPreviewFromLogoContainerWithSize(targetFrame.size) + } + + preview?.frame = targetFrame + preview?.frame.origin.x = coordinator.contentContainer.frame.width * CGFloat(modifier) + } + + private func createPreviewFromImage(_ image: UIImage) { + let imageView = UIImageView(image: image) + imageView.contentMode = .scaleAspectFill + coordinator.contentContainer.addSubview(imageView) + preview = imageView + } + + private func createPreviewFromLogoContainerWithSize(_ size: CGSize) { + let origin = coordinator.contentContainer.convert(CGPoint.zero, to: coordinator.logoContainer) + let snapshotFrame = CGRect(origin: origin, size: size) + let isHidden = coordinator.logoContainer.isHidden + coordinator.logoContainer.isHidden = false + if let snapshotView = coordinator.logoContainer.resizableSnapshotView(from: snapshotFrame, + afterScreenUpdates: true, + withCapInsets: .zero) { + coordinator.contentContainer.addSubview(snapshotView) + preview = snapshotView + } + coordinator.logoContainer.isHidden = isHidden + } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + switch state { + case .idle: + state = .starting(scrollView.contentOffset) + + default: break + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + defer { + cleanUpViews() + state = .idle + } + + let point = CGPoint(x: coordinator.navigationBarCollectionView.bounds.midX, + y: coordinator.navigationBarCollectionView.bounds.midY) + + guard let index = coordinator.navigationBarCollectionView.indexPathForItem(at: point)?.row else { + assertionFailure("invalid index") + return + } + feedbackGenerator.selectionChanged() + if index >= tabsModel.count { + newTab() + } else { + selectTab(index) + } + } + + private func cleanUpViews() { + currentView?.transform = .identity + currentView = nil + preview?.removeFromSuperview() + } + +} + +// MARK: Public Interface +extension SwipeTabsCoordinator { + + func refresh(tabsModel: TabsModel, scrollToSelected: Bool = false) { + self.tabsModel = tabsModel + coordinator.navigationBarCollectionView.reloadData() + + updateLayout() + + if scrollToSelected { + scrollToCurrent() + } + } + + func addressBarPositionChanged(isTop: Bool) { + if isTop { + collectionView.horizontalScrollIndicatorInsets.bottom = -1.5 + collectionView.hitTestInsets.top = -12 + collectionView.hitTestInsets.bottom = 0 + } else { + collectionView.horizontalScrollIndicatorInsets.bottom = collectionView.frame.height - 7.5 + collectionView.hitTestInsets.top = 0 + collectionView.hitTestInsets.bottom = -12 + } + } + +} + +// MARK: UICollectionViewDataSource +extension SwipeTabsCoordinator: UICollectionViewDataSource { + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + guard isEnabled else { return 1 } + let extras = tabsModel.tabs.last?.link != nil ? 1 : 0 // last tab is not a home page, so let's add one + let count = tabsModel.count + extras + return count + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "omnibar", for: indexPath) as? OmniBarCell else { + fatalError("Not \(OmniBarCell.self)") + } + + if !isEnabled || tabsModel.currentIndex == indexPath.row { + cell.omniBar = coordinator.omniBar + } else { + cell.omniBar = OmniBar.loadFromXib() + cell.omniBar?.translatesAutoresizingMaskIntoConstraints = false + cell.updateConstraints() + cell.omniBar?.decorate(with: ThemeManager.shared.currentTheme) + + cell.omniBar?.showSeparator() + if self.appSettings.currentAddressBarPosition.isBottom { + cell.omniBar?.moveSeparatorToTop() + } else { + cell.omniBar?.moveSeparatorToBottom() + } + + if let url = tabsModel.safeGetTabAt(indexPath.row)?.link?.url { + cell.omniBar?.startBrowsing() + cell.omniBar?.refreshText(forUrl: url) + } + + } + + return cell + } + +} + +class OmniBarCell: UICollectionViewCell { + + weak var omniBar: OmniBar? { + didSet { + subviews.forEach { $0.removeFromSuperview() } + if let omniBar { + addSubview(omniBar) + + NSLayoutConstraint.activate([ + constrainView(omniBar, by: .leadingMargin), + constrainView(omniBar, by: .trailingMargin), + constrainView(omniBar, by: .top), + constrainView(omniBar, by: .bottom), + ]) + + } + } + } + + override func updateConstraints() { + super.updateConstraints() + let left = superview?.safeAreaInsets.left ?? 0 + let right = superview?.safeAreaInsets.right ?? 0 + omniBar?.updateOmniBarPadding(left: left, right: right) + } + +} + +extension TabsModel { + + func safeGetTabAt(_ index: Int) -> Tab? { + guard tabs.indices.contains(index) else { return nil } + return tabs[index] + } + +} diff --git a/DuckDuckGo/WebViewTransition.swift b/DuckDuckGo/WebViewTransition.swift index af7ffbc8fe..0b25b7ba38 100644 --- a/DuckDuckGo/WebViewTransition.swift +++ b/DuckDuckGo/WebViewTransition.swift @@ -165,7 +165,6 @@ class ToWebViewTransition: WebViewTransition { scrollIfOutsideViewport(collectionView: tabSwitcherViewController.collectionView, rowIndex: rowIndex, attributes: layoutAttr) UIView.animate(withDuration: TabSwitcherTransition.Constants.duration, animations: { - let frame = CGRect(x: 0, y: webView.scrollView.contentInset.top, width: webViewFrame.width, height: webViewFrame.height) self.imageContainer.frame = mainViewController.viewCoordinator.contentContainer.frame self.imageContainer.layer.cornerRadius = 0 From 8521e9d385a203f08f58ba19e40e093b6aee6232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 9 Feb 2024 12:43:18 +0100 Subject: [PATCH 020/245] PR test (#2464) --- .github/workflows/pr-task-url.yml | 136 ++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 .github/workflows/pr-task-url.yml diff --git a/.github/workflows/pr-task-url.yml b/.github/workflows/pr-task-url.yml new file mode 100644 index 0000000000..d77a259008 --- /dev/null +++ b/.github/workflows/pr-task-url.yml @@ -0,0 +1,136 @@ +name: Asana PR Task URL + +on: + pull_request: + types: [opened, edited, closed, unlabeled, synchronize] + +jobs: + + # This job is used to assert that the task linked in the PR description belongs to the specified project (App Board). + assert-project-membership: + + name: Check App Board Project Membership + + runs-on: ubuntu-latest + + outputs: + task_id: ${{ steps.get-task-id.outputs.task_id }} + failure: ${{ steps.check-task-url.outputs.failure }} + + steps: + - name: Get Task ID + id: get-task-id + env: + BODY: ${{ github.event.pull_request.body }} + run: | + task_id=$(grep -i "task/issue url.*https://app.asana.com/" <<< "$BODY" \ + | sed -E 's|.*https://(.*)|\1|' \ + | cut -d '/' -f 4) + echo "task_id=$task_id" >> $GITHUB_OUTPUT + + - name: Check Task URL + id: check-task-url + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + ASANA_PROJECT_ID: ${{ vars.IOS_APP_BOARD_ASANA_PROJECT_ID }} + run: | + project_ids="$(curl -fLSs "https://app.asana.com/api/1.0/tasks/${{ steps.get-task-id.outputs.task_id }}?opt_fields=projects" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + | jq -r .data.projects[].gid)" + + if grep -q "\b${{ env.ASANA_PROJECT_ID }}\b" <<< $project_ids; then + echo "failure=0" >> $GITHUB_OUTPUT + else + echo "failure=1" >> $GITHUB_OUTPUT + fi + + # If a task URL is present, but task is missing from the App Board project, add a comment to the PR and fail the check. + # Otherwise, delete the comment and pass the check. + update-project-membership-report: + + name: App Board Project Membership Report + + runs-on: ubuntu-latest + if: github.event.action != 'closed' + + needs: [assert-project-membership] + + steps: + - name: Comment on the PR + if: ${{ needs.assert-project-membership.outputs.task_id && needs.assert-project-membership.outputs.failure == '1' }} + env: + ASANA_PROJECT_NAME: ${{ vars.IOS_APP_BOARD_ASANA_PROJECT_NAME }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: asana-task-check-status + message: | + :no_entry_sign: The Asana task linked in the PR description is not added to ${{ env.ASANA_PROJECT_NAME }} project. + 1. Verify that the correct task is linked in the PR. + * :warning: Please use the actual implementation task, rather than the Code Review subtask. + 2. Verify that the task is added to ${{ env.ASANA_PROJECT_NAME }} project. + 3. When ready, remove the `bot: not in app board` label to retrigger the check. + + - name: Add a label to the PR + if: ${{ needs.assert-project-membership.outputs.task_id && needs.assert-project-membership.outputs.failure == '1' }} + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['bot: not in app board'] + }) + + - name: Delete comment on the PR + if: ${{ needs.assert-project-membership.outputs.task_id == '' || needs.assert-project-membership.outputs.failure == '0' }} + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: asana-task-check-status + delete: true + + - name: Remove the label from the PR + if: ${{ needs.assert-project-membership.outputs.task_id == '' || needs.assert-project-membership.outputs.failure == '0' }} + uses: actions/github-script@v7 + with: + script: | + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'bot: not in app board' + }); + } catch (error) { + if (error.status !== 404) { + throw error; + } + } + + - name: Report status + if: ${{ needs.assert-project-membership.outputs.task_id }} + run: exit ${{ needs.assert-project-membership.outputs.failure }} + + # When a PR is merged, move the task to the Waiting for Release section of the App Board. + mark-waiting-for-release: + + name: Move to Waiting for Release on Merge + + runs-on: ubuntu-latest + if: github.event.action == 'closed' && github.event.pull_request.merged == true + + needs: [assert-project-membership] + + steps: + - name: Move to Waiting for Release + if: ${{ needs.assert-project-membership.result.failure == '0' }} + env: + ASANA_ACCESS_TOKEN: ${{ secrets.ASANA_ACCESS_TOKEN }} + ASANA_PROJECT_ID: ${{ vars.IOS_APP_BOARD_ASANA_PROJECT_ID }} + run: | + curl -fLSs -X POST "https://app.asana.com/api/1.0/sections/${{ vars.IOS_APP_BOARD_WAITING_FOR_RELEASE_SECTION_ID }}/addTask" \ + -H "Authorization: Bearer ${{ env.ASANA_ACCESS_TOKEN }}" \ + -H "Content-Type: application/json" \ + --output /dev/null \ + -d "{\"data\": {\"task\": \"${{ needs.assert-project-membership.outputs.task_id }}\"}}" From d7f71113e0c95aa7e403952bd1fb5b8151059d61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 9 Feb 2024 12:55:58 +0100 Subject: [PATCH 021/245] Add strict checking for Asana link in PR description (#2463) From 18663c327ef9d5a9bddba4a9ef507d611553deb6 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 9 Feb 2024 15:25:26 +0100 Subject: [PATCH 022/245] Daniel/subscriptions/8.itp (#2427) Task/Issue URL: https://app.asana.com/0/72649045549333/1205043809052693/f Description: Implements ITP for subscriptions Implements navigating back in WebViews Shows/Hide navigation bar on WebViews depending on scroll position Subscription WebViews are now shown in a sheet for better navigation/usability Includes several improvements to the subscription flow for memory management and etc. Allows navigating to tel: FaceTime: and etc schemas Properly manages to navigate to new windows --- DuckDuckGo.xcodeproj/project.pbxproj | 66 +++++++- DuckDuckGo/MainViewController+Segues.swift | 2 - DuckDuckGo/SettingsSubscriptionView.swift | 39 ++++- DuckDuckGo/SettingsViewModel.swift | 6 +- .../AsyncHeadlessWebView.swift | 64 ++++++++ .../AsyncHeadlessWebViewModel.swift | 66 ++++++++ .../HeadlessWebView.swift | 81 +++++++++ .../HeadlessWebViewCoordinator.swift | 138 ++++++++++++++++ .../HeadlessWebViewNavCoordinator.swift | 58 +++++++ .../Extensions/View+TopMostController.swift | 40 +++++ .../Subscription.xcassets/Contents.json | 6 + .../Contents.json | 15 ++ .../Sync-128.svg | 21 +++ .../Contents.json | 25 +++ .../share-dark.pdf | Bin 0 -> 3115 bytes .../SubscriptionShareIcon.imageset/share.pdf | Bin 0 -> 3162 bytes .../Subscription/URL+Subscription.swift | 5 + ...IdentityTheftRestorationPagesFeature.swift | 74 +++++++++ ...ntityTheftRestorationPagesUserScript.swift | 70 ++++++++ .../UserScripts/Subscription.swift | 24 +++ ...scriptionPagesUseSubscriptionFeature.swift | 5 +- .../SubscriptionEmailViewModel.swift | 14 +- .../ViewModel/SubscriptionFlowViewModel.swift | 87 ++++++++-- .../ViewModel/SubscriptionITPViewModel.swift | 144 ++++++++++++++++ .../SubscriptionRestoreViewModel.swift | 5 - .../Subscription/Views/HeadlessWebView.swift | 131 --------------- .../Views/RootPresentationMode.swift | 45 +++++ .../Views/SubscriptionEmailView.swift | 27 +-- .../Views/SubscriptionFlowView.swift | 147 +++++++++++++---- .../Views/SubscriptionITPView.swift | 155 ++++++++++++++++++ .../Views/SubscriptionRestoreView.swift | 34 ++-- .../Views/SubscriptionSettingsView.swift | 9 +- DuckDuckGo/UserText.swift | 3 + DuckDuckGo/en.lproj/Localizable.strings | 6 + 34 files changed, 1366 insertions(+), 246 deletions(-) create mode 100644 DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift create mode 100644 DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift create mode 100644 DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift create mode 100644 DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift create mode 100644 DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewNavCoordinator.swift create mode 100644 DuckDuckGo/Subscription/Extensions/View+TopMostController.swift create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Sync-128.svg create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/share-dark.pdf create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/share.pdf create mode 100644 DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift create mode 100644 DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift create mode 100644 DuckDuckGo/Subscription/UserScripts/Subscription.swift create mode 100644 DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift delete mode 100644 DuckDuckGo/Subscription/Views/HeadlessWebView.swift create mode 100644 DuckDuckGo/Subscription/Views/RootPresentationMode.swift create mode 100644 DuckDuckGo/Subscription/Views/SubscriptionITPView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a3f3ca0348..8a62e8e494 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -778,17 +778,24 @@ D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */; }; D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; }; D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */; }; + D65CEA702B6AC6C9008A759B /* Subscription.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */; }; D664C7B62B289AA200CBFA76 /* SubscriptionFlowViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */; }; D664C7B72B289AA200CBFA76 /* Subscription.storekit in Resources */ = {isa = PBXBuildFile; fileRef = D664C7952B289AA000CBFA76 /* Subscription.storekit */; }; D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */; }; D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */; }; D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */; }; - D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */; }; + D664C7C92B289AA200CBFA76 /* AsyncHeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7AF2B289AA000CBFA76 /* AsyncHeadlessWebView.swift */; }; D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */; }; D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */; }; D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D664C7DC2B28A02800CBFA76 /* StoreKit.framework */; }; + D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */; }; + D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */; }; + D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; }; + D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */; }; + D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92C2B696945008E2FF2 /* Subscription.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; + D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8B2B291CA90054390C /* URL+Subscription.swift */; }; D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */; }; @@ -804,6 +811,8 @@ D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9C2B291CA90054390C /* APIService.swift */; }; D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9D2B291CA90054390C /* AuthService.swift */; }; D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; }; + D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */; }; + D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -822,6 +831,9 @@ D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C672B23B6A3006C8AFB /* FontSettings.swift */; }; D6F93E3C2B4FFA97004C268D /* SubscriptionDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */; }; D6F93E3E2B50A8A0004C268D /* SubscriptionSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */; }; + D6FEB8B12B7498A300C3615F /* HeadlessWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FEB8B02B7498A300C3615F /* HeadlessWebView.swift */; }; + D6FEB8B32B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FEB8B22B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift */; }; + D6FEB8B52B74994000C3615F /* HeadlessWebViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6FEB8B42B74994000C3615F /* HeadlessWebViewCoordinator.swift */; }; EA39B7E2268A1A35000C62CD /* privacy-reference-tests in Resources */ = {isa = PBXBuildFile; fileRef = EA39B7E1268A1A35000C62CD /* privacy-reference-tests */; }; EAB19EDA268963510015D3EA /* DomainMatchingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */; }; EE0153E12A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */; }; @@ -2430,17 +2442,24 @@ D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailView.swift; sourceTree = ""; }; D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailViewModel.swift; sourceTree = ""; }; D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsViewModel.swift; sourceTree = ""; }; + D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Subscription.xcassets; sourceTree = ""; }; D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowViewModel.swift; sourceTree = ""; }; D664C7952B289AA000CBFA76 /* Subscription.storekit */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = Subscription.storekit; sourceTree = ""; }; D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WKUserContentController+Handler.swift"; sourceTree = ""; }; D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseInProgressView.swift; sourceTree = ""; }; D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionFlowView.swift; sourceTree = ""; }; - D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; + D664C7AF2B289AA000CBFA76 /* AsyncHeadlessWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebView.swift; sourceTree = ""; }; D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUserScript.swift; sourceTree = ""; }; D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPagesUseSubscriptionFeature.swift; sourceTree = ""; }; D664C7DC2B28A02800CBFA76 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; + D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionITPView.swift; sourceTree = ""; }; + D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionITPViewModel.swift; sourceTree = ""; }; + D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; + D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesFeature.swift; sourceTree = ""; }; + D668D92C2B696945008E2FF2 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; + D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TopMostController.swift"; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; D6D12C8B2B291CA90054390C /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPurchaseEnvironment.swift; sourceTree = ""; }; @@ -2456,6 +2475,8 @@ D6D12C9C2B291CA90054390C /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; D6D12C9D2B291CA90054390C /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; + D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; + D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -2474,6 +2495,9 @@ D6E83C672B23B6A3006C8AFB /* FontSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSettings.swift; sourceTree = ""; }; D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionDebugViewController.swift; sourceTree = ""; }; D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsView.swift; sourceTree = ""; }; + D6FEB8B02B7498A300C3615F /* HeadlessWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebView.swift; sourceTree = ""; }; + D6FEB8B22B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebViewNavCoordinator.swift; sourceTree = ""; }; + D6FEB8B42B74994000C3615F /* HeadlessWebViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlessWebViewCoordinator.swift; sourceTree = ""; }; EA39B7E1268A1A35000C62CD /* privacy-reference-tests */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = "privacy-reference-tests"; path = "submodules/privacy-reference-tests"; sourceTree = SOURCE_ROOT; }; EAB19ED9268963510015D3EA /* DomainMatchingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainMatchingTests.swift; sourceTree = ""; }; EE0153E02A6EABE0002A8B26 /* NetworkProtectionConvenienceInitialisers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionConvenienceInitialisers.swift; sourceTree = ""; }; @@ -4519,12 +4543,14 @@ D664C7922B289AA000CBFA76 /* Subscription */ = { isa = PBXGroup; children = ( + D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, D664C7952B289AA000CBFA76 /* Subscription.storekit */, D664C7932B289AA000CBFA76 /* ViewModel */, D664C7AC2B289AA000CBFA76 /* Views */, D664C7B02B289AA000CBFA76 /* UserScripts */, D664C7962B289AA000CBFA76 /* Extensions */, D6D12C8A2B291CA90054390C /* Subscription */, + D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, ); path = Subscription; sourceTree = ""; @@ -4534,6 +4560,7 @@ children = ( D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */, D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */, + D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */, D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, ); @@ -4544,6 +4571,7 @@ isa = PBXGroup; children = ( D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, + D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */, ); path = Extensions; sourceTree = ""; @@ -4551,12 +4579,13 @@ D664C7AC2B289AA000CBFA76 /* Views */ = { isa = PBXGroup; children = ( - D664C7AF2B289AA000CBFA76 /* HeadlessWebView.swift */, D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, - D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */, D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */, + D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */, + D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, + D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */, ); path = Views; sourceTree = ""; @@ -4564,7 +4593,10 @@ D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( + D668D92C2B696945008E2FF2 /* Subscription.swift */, + D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, ); path = UserScripts; @@ -4623,6 +4655,18 @@ path = Services; sourceTree = ""; }; + D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */ = { + isa = PBXGroup; + children = ( + D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */, + D664C7AF2B289AA000CBFA76 /* AsyncHeadlessWebView.swift */, + D6FEB8B02B7498A300C3615F /* HeadlessWebView.swift */, + D6FEB8B22B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift */, + D6FEB8B42B74994000C3615F /* HeadlessWebViewCoordinator.swift */, + ); + path = AsyncHeadlessWebview; + sourceTree = ""; + }; D6E83C322B1F1279006C8AFB /* Sections */ = { isa = PBXGroup; children = ( @@ -6095,6 +6139,7 @@ AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, + D65CEA702B6AC6C9008A759B /* Subscription.xcassets in Resources */, F1E4A4451EE89460006F2EAE /* Bookmarks.storyboard in Resources */, AA4D6ABB23DE4D15007E8790 /* AppIconYellow40x40@2x.png in Resources */, 84E341A01E2F7EFB00BDBA6F /* LaunchScreen.storyboard in Resources */, @@ -6475,6 +6520,7 @@ files = ( EE4FB1862A28CE7200E5CBA7 /* NetworkProtectionStatusView.swift in Sources */, C17B59592A03AAD30055F2D1 /* PasswordGenerationPromptViewModel.swift in Sources */, + D6FEB8B52B74994000C3615F /* HeadlessWebViewCoordinator.swift in Sources */, 8528AE81212F15D600D0BD74 /* AppRatingPrompt.xcdatamodeld in Sources */, 1E24295E293F57FA00584836 /* LottieView.swift in Sources */, 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */, @@ -6505,6 +6551,7 @@ 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, + D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */, F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */, 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */, 4BBBBA922B03291700D965DA /* VPNWaitlistUserText.swift in Sources */, @@ -6516,6 +6563,7 @@ 9874F9EE2187AFCE00CAF33D /* Themable.swift in Sources */, F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */, 3151F0F02735802800226F58 /* VoiceSearchViewController.swift in Sources */, + D6FEB8B32B74990D00C3615F /* HeadlessWebViewNavCoordinator.swift in Sources */, 85BDC310243359040053DB07 /* FindInPageUserScript.swift in Sources */, F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */, 984D035824ACCC6F0066CFB8 /* TabViewListCell.swift in Sources */, @@ -6545,7 +6593,7 @@ 85047C752A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift in Sources */, F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, - D664C7C92B289AA200CBFA76 /* HeadlessWebView.swift in Sources */, + D664C7C92B289AA200CBFA76 /* AsyncHeadlessWebView.swift in Sources */, F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */, D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, @@ -6586,6 +6634,7 @@ EE276BEA2A77F823009167B6 /* NetworkProtectionRootViewController.swift in Sources */, 310ECFDD282A8BB0005029B3 /* EnableAutofillSettingsTableViewCell.swift in Sources */, 1E908BF329827C480008C8F3 /* AutoconsentManagement.swift in Sources */, + D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */, CB9B8739278C8E72001F4906 /* WidgetEducationViewController.swift in Sources */, F4D9C4FA25117A0F00814B71 /* HomeMessageStorage.swift in Sources */, D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */, @@ -6602,6 +6651,7 @@ CB258D1329A4F24E00DEBA24 /* ConfigurationStore.swift in Sources */, 85058370219F424500ED4EDB /* SearchBarExtension.swift in Sources */, 310D09212799FD1A00DC0060 /* MIMEType.swift in Sources */, + D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */, D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */, BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */, F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */, @@ -6609,6 +6659,7 @@ 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */, F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */, + D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */, C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */, 982E5630222C3D5B008D861B /* FeedbackPickerViewController.swift in Sources */, 37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */, @@ -6649,6 +6700,7 @@ F4F6DFB426E6B63700ED7E12 /* BookmarkFolderCell.swift in Sources */, D6F93E3E2B50A8A0004C268D /* SubscriptionSettingsView.swift in Sources */, 851B12CC22369931004781BC /* AtbAndVariantCleanup.swift in Sources */, + D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */, 85F2FFCF2211F8E5006BB258 /* TabSwitcherViewController+KeyCommands.swift in Sources */, 3157B43327F497E90042D3D7 /* SaveLoginView.swift in Sources */, F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */, @@ -6701,6 +6753,7 @@ 31C70B5528045E3500FB6AD1 /* SecureVaultErrorReporter.swift in Sources */, F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */, 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, + D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */, EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */, @@ -6783,6 +6836,7 @@ 980891A222369ADB00313A70 /* FeedbackUserText.swift in Sources */, 4BCD14692B05BDD5000B1E4C /* AppDelegate+Waitlists.swift in Sources */, 988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */, + D6FEB8B12B7498A300C3615F /* HeadlessWebView.swift in Sources */, 850ABD012AC3961100A733DF /* MainViewController+Segues.swift in Sources */, 9817C9C321EF594700884F65 /* AutoClear.swift in Sources */, 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */, @@ -6883,11 +6937,13 @@ D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, + D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */, EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, + D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */, AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 0d994f5699..94b4be4591 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -244,8 +244,6 @@ extension MainViewController { let navController = UINavigationController(rootViewController: settingsController) navController.applyTheme(ThemeManager.shared.currentTheme) settingsController.modalPresentationStyle = .automatic - - settingsController.isModalInPresentation = true present(navController, animated: true) { completion?(settingsViewModel) diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 11e681147d..b74e57c6b9 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -25,6 +25,10 @@ import UIKit struct SettingsSubscriptionView: View { @EnvironmentObject var viewModel: SettingsViewModel + @StateObject var subscriptionFlowViewModel = SubscriptionFlowViewModel() + @State var isShowingsubScriptionFlow = false + @State var isShowingDBP = false + @State var isShowingITP = false private var subscriptionDescriptionView: some View { VStack(alignment: .leading) { @@ -51,11 +55,11 @@ struct SettingsSubscriptionView: View { private var purchaseSubscriptionView: some View { return Group { SettingsCustomCell(content: { subscriptionDescriptionView }) - let viewModel = SubscriptionFlowViewModel(onFeatureSelected: { value in - self.viewModel.onAppearNavigationTarget = value - }) - NavigationLink(destination: SubscriptionFlowView(viewModel: viewModel)) { - SettingsCustomCell(content: { learnMoreView }) + SettingsCustomCell(content: { learnMoreView }, + action: { isShowingsubScriptionFlow = true }, + isButton: true ) + .sheet(isPresented: $isShowingsubScriptionFlow) { + SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() } } } @@ -68,17 +72,24 @@ struct SettingsSubscriptionView: View { disclosureIndicator: true, isButton: true) + /* NavigationLink(destination: Text("Data Broker Protection"), isActive: $viewModel.shouldNavigateToDBP) { SettingsCellView(label: UserText.settingsPProDBPTitle, subtitle: UserText.settingsPProDBPSubTitle) } + */ - NavigationLink(destination: Text("Identity Theft Restoration"), isActive: $viewModel.shouldNavigateToITP) { - SettingsCellView(label: UserText.settingsPProITRTitle, subtitle: UserText.settingsPProITRSubTitle) + SettingsCellView(label: UserText.settingsPProITRTitle, + subtitle: UserText.settingsPProITRSubTitle, + action: { isShowingITP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingITP) { + SubscriptionITPView() } + - NavigationLink(destination: SubscriptionSettingsView(viewModel: SubscriptionSettingsViewModel())) { + NavigationLink(destination: SubscriptionSettingsView()) { SettingsCustomCell(content: { manageSubscriptionView }) } + } } @@ -91,6 +102,18 @@ struct SettingsSubscriptionView: View { } else { purchaseSubscriptionView } + + } + // Refresh subscription when dismissing the Subscription Flow + .onChange(of: isShowingsubScriptionFlow, perform: { value in + if !value { + Task { viewModel.onAppear() } + } + }) + + .onReceive(subscriptionFlowViewModel.$selectedFeature) { value in + guard let value else { return } + viewModel.onAppearNavigationTarget = value } } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 899b4eef6f..c379365941 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -91,7 +91,7 @@ final class SettingsViewModel: ObservableObject { // Used to automatically navigate on Appear to a specific section enum SettingsSection: String { - case none, netP, dbp, itp + case none, netP, dbp, itr } @Published var onAppearNavigationTarget: SettingsSection @@ -422,13 +422,13 @@ extension SettingsViewModel { private func navigateOnAppear() { // We need a short delay to let the SwifttUI view lifecycle complete // Otherwise the transition can be inconsistent - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { switch self.onAppearNavigationTarget { case .netP: self.presentLegacyView(.netP) case .dbp: self.shouldNavigateToDBP = true - case .itp: + case .itr: self.shouldNavigateToITP = true default: break diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift new file mode 100644 index 0000000000..74b5224f9d --- /dev/null +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift @@ -0,0 +1,64 @@ +// +// AsyncHeadlessWebView.swift +// DuckDuckGo +// +// Copyright © 2023 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 WebKit +import UserScript +import SwiftUI +import DesignResourcesKit +import Core + +struct AsyncHeadlessWebViewSettings { + let bounces: Bool + + init(bounces: Bool = false) { + self.bounces = bounces + } +} + +struct AsyncHeadlessWebView: View { + @StateObject var viewModel: AsyncHeadlessWebViewViewModel + + var body: some View { + GeometryReader { geometry in + HeadlessWebView( + userScript: viewModel.userScript, + subFeature: viewModel.subFeature, + settings: viewModel.settings, + onScroll: { newPosition in + viewModel.updateScrollPosition(newPosition) + }, + onURLChange: { newURL in + viewModel.url = newURL + }, + onCanGoBack: { value in + viewModel.canGoBack = value + }, + onCanGoForward: { value in + viewModel.canGoForward = value + }, + onContentType: { value in + viewModel.contentType = value + }, + navigationCoordinator: viewModel.navigationCoordinator + ) + .frame(width: geometry.size.width, height: geometry.size.height) + } + } +} diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift new file mode 100644 index 0000000000..304908891c --- /dev/null +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift @@ -0,0 +1,66 @@ +// +// AsyncHeadlessWebViewModel.swift +// DuckDuckGo +// +// 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 UserScript +import Core +import Combine + +final class AsyncHeadlessWebViewViewModel: ObservableObject { + let userScript: UserScriptMessaging? + let subFeature: Subfeature? + let settings: AsyncHeadlessWebViewSettings + + private var initialScrollPositionSubject = PassthroughSubject() + private var subsequentScrollPositionSubject = PassthroughSubject() + private var cancellables = Set() + private var isFirstUpdate = true + private var initialDelay = 1 + + @Published var scrollPosition: CGPoint = .zero + @Published var url: URL? + @Published var canGoBack: Bool = false + @Published var canGoForward: Bool = false + @Published var contentType: String = "" + + var navigationCoordinator = HeadlessWebViewNavCoordinator(webView: nil) + + init(userScript: UserScriptMessaging?, subFeature: Subfeature?, settings: AsyncHeadlessWebViewSettings) { + self.userScript = userScript + self.subFeature = subFeature + self.settings = settings + + // Delayed publishing first update for scrollPosition + // To avoid publishing events on view updates + initialScrollPositionSubject + .delay(for: .seconds(initialDelay), scheduler: RunLoop.main) + .merge(with: subsequentScrollPositionSubject) + .assign(to: &$scrollPosition) + } + + func updateScrollPosition(_ newPosition: CGPoint) { + if isFirstUpdate { + initialScrollPositionSubject.send(newPosition) + isFirstUpdate = false + } else { + subsequentScrollPositionSubject.send(newPosition) + } + } + + +} diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift new file mode 100644 index 0000000000..5aa39ae262 --- /dev/null +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift @@ -0,0 +1,81 @@ +// +// HeadlessWebView.swift +// DuckDuckGo +// +// 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 SwiftUI +import WebKit +import UserScript + +struct HeadlessWebView: UIViewRepresentable { + let userScript: UserScriptMessaging? + let subFeature: Subfeature? + let settings: AsyncHeadlessWebViewSettings + var onScroll: ((CGPoint) -> Void)? + var onURLChange: ((URL) -> Void)? + var onCanGoBack: ((Bool) -> Void)? + var onCanGoForward: ((Bool) -> Void)? + var onContentType: ((String) -> Void)? + var navigationCoordinator: HeadlessWebViewNavCoordinator + + + func makeUIView(context: Context) -> WKWebView { + let configuration = WKWebViewConfiguration() + configuration.userContentController = makeUserContentController() + + let webView = WKWebView(frame: .zero, configuration: configuration) + + navigationCoordinator.webView = webView + webView.uiDelegate = context.coordinator + webView.scrollView.delegate = context.coordinator + webView.scrollView.bounces = settings.bounces + webView.navigationDelegate = context.coordinator + +#if DEBUG + if #available(iOS 16.4, *) { + webView.isInspectable = true + } +#endif + + context.coordinator.setupWebViewObservation(webView) + return webView + } + + func updateUIView(_ uiView: WKWebView, context: Context) {} + + func makeCoordinator() -> HeadlessWebViewCoordinator { + HeadlessWebViewCoordinator(self, + onScroll: onScroll, + onURLChange: onURLChange, + onCanGoBack: onCanGoBack, + onCanGoForward: onCanGoForward, + onContentType: onContentType) + } + + @MainActor + private func makeUserContentController() -> WKUserContentController { + let userContentController = WKUserContentController() + if let userScript, let subFeature { + userContentController.addUserScript(userScript.makeWKUserScriptSync()) + userContentController.addHandler(userScript) + userScript.registerSubfeature(delegate: subFeature) + } + return userContentController + } + +} diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift new file mode 100644 index 0000000000..a2fb375dc7 --- /dev/null +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift @@ -0,0 +1,138 @@ +// +// HeadlessWebViewCoordinator.swift +// DuckDuckGo +// +// 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 WebKit + +final class HeadlessWebViewCoordinator: NSObject { + var parent: HeadlessWebView + var onScroll: ((CGPoint) -> Void)? + var onURLChange: ((URL) -> Void)? + var onCanGoBack: ((Bool) -> Void)? + var onCanGoForward: ((Bool) -> Void)? + var onContentType: ((String) -> Void)? + + private var lastURL: URL? + + enum Constants { + static let contentTypeJS = "document.contentType" + static let externalSchemes = ["tel", "sms", "facetime"] + } + + private var webViewURLObservation: NSKeyValueObservation? + private var webViewCanGoBackObservation: NSKeyValueObservation? + private var webViewCanGoForwardObservation: NSKeyValueObservation? + + init(_ parent: HeadlessWebView, + onScroll: ((CGPoint) -> Void)?, + onURLChange: ((URL) -> Void)?, + onCanGoBack: ((Bool) -> Void)?, + onCanGoForward: ((Bool) -> Void)?, + onContentType: ((String) -> Void)?) { + self.parent = parent + self.onScroll = onScroll + self.onURLChange = onURLChange + self.onCanGoBack = onCanGoBack + self.onCanGoForward = onCanGoForward + self.onContentType = onContentType + } + + func setupWebViewObservation(_ webView: WKWebView) { + webViewURLObservation = webView.observe(\.url, options: [.new]) { [weak self] _, change in + if let newURL = change.newValue as? URL { + self?.onURLChange?(newURL) + self?.onCanGoBack?(webView.canGoBack) + } + } + + webViewCanGoBackObservation = webView.observe(\.canGoBack, options: [.new]) { [weak self] _, change in + if let canGoBack = change.newValue { + self?.onCanGoBack?(canGoBack) + } + } + + webViewCanGoForwardObservation = webView.observe(\.canGoForward, options: [.new]) { [weak self] _, change in + if let onCanGoForward = change.newValue { + self?.onCanGoForward?(onCanGoForward) + } + } + } + +} + +extension HeadlessWebViewCoordinator: WKUIDelegate {} + +extension HeadlessWebViewCoordinator: UIScrollViewDelegate { + func scrollViewDidScroll(_ scrollView: UIScrollView) { + let contentOffset = scrollView.contentOffset + onScroll?(contentOffset) + } +} + +extension HeadlessWebViewCoordinator: WKNavigationDelegate { + + func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { + // Force all requests for new windows or frame to be loaded in the View Itself (No popups or new windows) + webView.load(navigationAction.request) + return nil + } + + func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + if let url = webView.url, url != lastURL { + onURLChange?(url) + lastURL = url + if let onCanGoBack { + onCanGoBack(webView.canGoBack) + } + if let onCanGoForward { + onCanGoForward(webView.canGoForward) + } + } + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + webView.evaluateJavaScript(Constants.contentTypeJS) { result, error in + guard error == nil, let contentType = result as? String else { + return + } + self.onContentType?(contentType) + } + } + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + guard let url = navigationAction.request.url else { + + decisionHandler(.allow) + return + } + + guard let scheme = url.scheme else { + decisionHandler(.cancel) + return + } + + if Constants.externalSchemes.contains(scheme) && UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) + decisionHandler(.cancel) + } else { + decisionHandler(.allow) + } + } + +} diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewNavCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewNavCoordinator.swift new file mode 100644 index 0000000000..94f7695be9 --- /dev/null +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewNavCoordinator.swift @@ -0,0 +1,58 @@ +// +// HeadlessWebViewNavCoordinator.swift +// DuckDuckGo +// +// 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 WebKit +import Core + +final class HeadlessWebViewNavCoordinator { + weak var webView: WKWebView? + + init(webView: WKWebView?) { + self.webView = webView + } + + func reload() async { + _ = await MainActor.run { + self.webView?.reload() + } + } + + func navigateTo(url: URL) { + guard let webView else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0) { + DefaultUserAgentManager.shared.update(webView: webView, isDesktop: false, url: url) + webView.load(URLRequest(url: url)) + } + } + + func goBack() async { + guard await webView?.canGoBack == true else { return } + _ = await MainActor.run { + self.webView?.goBack() + } + } + + func goForward() async { + guard await webView?.canGoForward == true else { return } + _ = await MainActor.run { + self.webView?.goForward() + } + } +} diff --git a/DuckDuckGo/Subscription/Extensions/View+TopMostController.swift b/DuckDuckGo/Subscription/Extensions/View+TopMostController.swift new file mode 100644 index 0000000000..2d26949767 --- /dev/null +++ b/DuckDuckGo/Subscription/Extensions/View+TopMostController.swift @@ -0,0 +1,40 @@ +// +// View+TopMostController.swift +// DuckDuckGo +// +// 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 + +extension View { + + // Grabs the topMost controller so we can properly present sheets anywhere + func topMostViewController() -> UIViewController? { + guard let keyWindow = UIApplication.shared.connectedScenes + .filter({ $0.activationState == .foregroundActive }) + .compactMap({ $0 as? UIWindowScene }) + .first?.windows + .filter({ $0.isKeyWindow }).first else { + return nil + } + + var topController = keyWindow.rootViewController + while let presentedController = topController?.presentedViewController { + topController = presentedController + } + return topController + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Contents.json new file mode 100644 index 0000000000..49070f6b76 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Sync-128.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Sync-128.svg b/DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Sync-128.svg new file mode 100644 index 0000000000..c00e6ee0ed --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/ManageSubscriptionHero.imageset/Sync-128.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/Contents.json new file mode 100644 index 0000000000..9c8f34e6e8 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "share.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "share-dark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/share-dark.pdf b/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/share-dark.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d647e1794694cde14008eebdc76487774c76c0f GIT binary patch literal 3115 zcmZWrO^+Kl487}D=n^0)5Lu+YfgnJ$+Z07xbnEmM^x)1;cDL}_t?d+Pe|;aN8O`iw z4#xP6L_U&_lwRCizkf|8DMi}q;~#%2t>3)SZ{Mo%a4UZmukp=K&%o1Xx3{SFhj>dr{s8xZ^ad-Vz=8=+h)jBhAO0qlH z=o>~p=8U+oC;O199a|z%XudgeqK5k5JaUCbaBQjWu~Zle1GZ{_hQT*O0I8J9cU~!7 zkvLa75Jk1f(u7{>06X0`nx(i9TeD0o87vuvkd<>Zla^Zyt#1caI-%a-`vpY*OTS2i z_l41SHBcy(kQf-6%t0C%a{tfh3 zIl=@4C{!T3Vmu+usI2sm3@tzbITPWBX3{PYY8_hiX#oM%6w%6BXqn7_Ca-mCO}GbW zy$=OCKyvCMsV{JfW?n%P5p3-sBr=BnX2Z*!PYQM&&OxmiXAeEZZ zZJQRP#=R=GsZxqONx?QHNSS6O%>Fup6gC~i^{Og^l_lI8q@RRA_eZVkgu}#&XX{-9 zE2K-HV?7U&_bCsI69uQ6mm|>OK#h2b90CfA+C&sqPDh7h)k{y~BgVI@CuWE;_F#ar z$5e#LmeF44jv(ePhz`~h$7aGnSoT6o4#OYn1E1zVIpUrW08VtF%;zyJ zP!GK-cv`p8z_PNZ5|GT3Aa$CX?(2~-sC)J48+A~>D!JE9m$vzaCzrW)^JAZUzxeqM z<3hOk_HZ~}&ido;xFF)${Pyo(qu$)??#2oDdAxnty*qx;k2XnDFS;S_lW<&~|=Y4@-nPfDuy!!^?J@_c+8U+I?*x35}KHrwOzq9x#(;LV4J Y|35-~ICbf6*XyHvj+t literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/share.pdf b/DuckDuckGo/Subscription/Subscription.xcassets/SubscriptionShareIcon.imageset/share.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cd5ff551f6c99c0ea1905008f1c1bb6d71f6748f GIT binary patch literal 3162 zcmZWr+in~;41L#E=p{f>AX<^S13`esxu9r^x^~}!K5WMm+r@gf-Q5&vzrKgkjAm>P z410V=A`i(!N-r+oe|$|QDMi}q!ykVtt>3)Sn-Aa4pRUi_^Vt5Q`e)c`YqFI`zkS<& z)VRNqZHgc5|7qOcp6|2+c!h=PbUu#T2eo-W{&PQ!SD)VLi|h4&!%6+EHXlgeo)nqt z!ou0~^18qEZ+`wprX*id)Wd`7%bvfShP_H{4{M4hQm{7Y2jxs6xB=*l6ENx>z`zts z5s)}0OWGNZ(OEB$Wn0Jts{HEK2^tiet867Zdkxk(3;(8+ zP^el@E-6a#WLuk)}>&L+KB^CtKj!{XJ0WsP+}R%R+?26q5m4kVO|$sDbZ zIvJ#y1E>H(&Os`f$SsOuil3+?lcV#|X+{+Qa?sh3i^0<)$cd7iSnFG}!SWOkV|S5J zDW9bhok(sKt6O^%NNuen==Iok3TU=2F}8aXJzHOkl~X3r0u~(kh851az_T~rmXKK^ zsSesIsJft>Q{ws^x&d{;D4@_;-WF^r$%2AK;Ud--i{Kj>J}Wl)cHjt#rTLy&LX4E* zNmkC$IIsk@D$qIZuHVW$QnIdEXC_Wbb_W}M!^p>+5f}DkA5yhrOC$=-H%Cs?P#>H} zuFwdME!91i3PWMQRt?ZF_+|(ol~Vc6E2S$E=V}L{s1{k8&`TX)r~5{;6c=J^mWd^U zC8H3sa*k%wa*Lt$?Vw60)H{5)farhe7isXmFq#xf$^{xl`6F~5k*45X1d=RrTYxx9 zXjh~L3Z)Vf14ENJNF!q|U|7hi$$FkibVQmgISc`@KCMDKW$WF_k~AXcB5S(8gpm+j zw7@1-eTa4D9qI9jzqvB2MBFLSvG`zJ3_B)An1BF<3S?J|C!`sbl^&9z1t=hABK*)y z+66+bLyJBwAfTEeT3HJ%lNr$DwQj8m_W-T;p+E;nPJJZx1y0e-D`+ButsR6MQe}r$ z4;X>TMaJOsU=Inv#)u=(V00I0X|y5{VKiWf zaf&btUBefi3RSNI4hm7!8rxxv!+Q>Swbg{^k}HdScn>~P0@7)~xes*)&1~fum#dwOeCTCT&$xkV;MIwoMCC<6afpR4GNCq+pv8q)f9C<|=grDQr53 z>s3_-D@(X$NIwaK?vGm635SUl&(^yJR!EmX$9f(l?@|fWf^nkYbn|iqIvl7GFOfq) zfl-@?!piCBaIAXiX?(=^cJ;&zQN|t&Q1+OLFxfKN>)a8<+y&9Wdg9nj7zoQ=Xvtyt zLw(@W9EiLeR3cF40We4069T}AE|mE+rUmMuR|QY&RvK7V_EZ9rc@m^fbJIN^34^** zU%pd&^{bNW-E?=GFM4vPYZpa^g8Ti?cUbO^-|zQ_^GSdH9rsB*o8SKZYt);|?d>=L zKaV%}+joa=`m?l2eU=wq!tQUgrK|CD_<9`1la?Fn)nxACus@G`c$91C3|?-Jz;u`( zTtnv=1X?b;Ki=Pfs+I_@^z7&2@D+RM8UGWIm8JN|k_33_4wt|$w&(5caQk@cx8wLl zS)Ei1Uw-QtP#q0;dI-}-9=-2S0?$Vf1{YHC1ai+-Wpv(?RWc-J=zQGX@5ZB&>izH@ y>3DfMJdCgO%ln&GEh(F;!{MwY;F{pg_5FV%*w@?r_IR%0aPd~Xc=6$vkN*S7%7AA8 literal 0 HcmV?d00001 diff --git a/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift b/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift index 2451ab5055..d8f35b7312 100644 --- a/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift +++ b/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift @@ -51,4 +51,9 @@ public extension URL { static var manageSubscriptionsIniOSAppStoreAppURL: URL { URL(string: "https://apps.apple.com/account/subscriptions")! } + + // MARK: - Identity Theft Protection + static var manageITP: URL { + URL(string: "https://abrown.duckduckgo.com/identity-theft-restoration")! + } } diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift new file mode 100644 index 0000000000..28aa9881f0 --- /dev/null +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -0,0 +1,74 @@ +// +// IdentityTheftRestorationPagesFeature.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if SUBSCRIPTION +import BrowserServicesKit +import Common +import Foundation +import WebKit +import UserScript +import Combine + +@available(iOS 15.0, *) +final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { + + struct Constants { + static let featureName = "useIdentityTheftRestoration" + static let os = "ios" + } + + struct OriginDomains { + static let duckduckgo = "duckduckgo.com" + static let abrown = "abrown.duckduckgo.com" + } + + struct Handlers { + static let getAccessToken = "getAccessToken" + } + + + var broker: UserScriptMessageBroker? + var featureName: String = Constants.featureName + + var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ + .exact(hostname: OriginDomains.duckduckgo), + .exact(hostname: OriginDomains.abrown) + ]) + + var originalMessage: WKScriptMessage? + + func with(broker: UserScriptMessageBroker) { + self.broker = broker + } + + func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + switch methodName { + case Handlers.getAccessToken: return getAccessToken + default: + return nil + } + } + + func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let authToken = AccountManager().authToken ?? "" + return Subscription(token: authToken) + } + +} +#endif diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift new file mode 100644 index 0000000000..cfbafec477 --- /dev/null +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesUserScript.swift @@ -0,0 +1,70 @@ +// +// IdentityTheftRestorationPagesUserScript.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if SUBSCRIPTION + +import BrowserServicesKit +import Common +import Combine +import Foundation +import WebKit +import UserScript + +/// +/// The user script that will be the broker for all identity theft protection features +/// +public final class IdentityTheftRestorationPagesUserScript: NSObject, UserScript, UserScriptMessaging { + public var source: String = "" + + public static let context = "identityTheftRestorationPages" + + // special pages messaging cannot be isolated as we'll want regular page-scripts to be able to communicate + public let broker = UserScriptMessageBroker(context: IdentityTheftRestorationPagesUserScript.context, requiresRunInPageContentWorld: false ) + + public let messageNames: [String] = [ + IdentityTheftRestorationPagesUserScript.context + ] + + public let injectionTime: WKUserScriptInjectionTime = .atDocumentStart + public let forMainFrameOnly = true + public let requiresRunInPageContentWorld = true +} + +extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandlerWithReply { + @MainActor + public func userContentController(_ userContentController: WKUserContentController, + didReceive message: WKScriptMessage) async -> (Any?, String?) { + let action = broker.messageHandlerFor(message) + do { + let json = try await broker.execute(action: action, original: message) + return (json, nil) + } catch { + // forward uncaught errors to the client + return (nil, error.localizedDescription) + } + } +} + +extension IdentityTheftRestorationPagesUserScript: WKScriptMessageHandler { + public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + // unsupported + } +} + +#endif diff --git a/DuckDuckGo/Subscription/UserScripts/Subscription.swift b/DuckDuckGo/Subscription/UserScripts/Subscription.swift new file mode 100644 index 0000000000..9306a531c5 --- /dev/null +++ b/DuckDuckGo/Subscription/UserScripts/Subscription.swift @@ -0,0 +1,24 @@ +// +// Subscription.swift +// DuckDuckGo +// +// 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 Subscription: Encodable { + let token: String +} diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 3b85bd54b5..780b317fbe 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -103,10 +103,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } } - struct Subscription: Encodable { - let token: String - } - + /// Values that the Frontend can use to determine the current state. // swiftlint:disable nesting struct SubscriptionValues: Codable { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 7e883f8e06..abe47f3fc5 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -36,15 +36,19 @@ final class SubscriptionEmailViewModel: ObservableObject { @Published var shouldReloadWebView = false @Published var activateSubscription = false @Published var managingSubscriptionEmail = false + @Published var webViewModel: AsyncHeadlessWebViewViewModel private var cancellables = Set() - init(userScript: SubscriptionPagesUserScript, - subFeature: SubscriptionPagesUseSubscriptionFeature, - accountManager: AccountManager) { + init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), + subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), + accountManager: AccountManager = AccountManager()) { self.userScript = userScript self.subFeature = subFeature self.accountManager = accountManager + self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, + subFeature: subFeature, + settings: AsyncHeadlessWebViewSettings(bounces: false)) initializeView() setupTransactionObservers() } @@ -76,5 +80,9 @@ final class SubscriptionEmailViewModel: ObservableObject { activateSubscription = true } + func loadURL() { + webViewModel.navigationCoordinator.navigateTo(url: emailURL ) + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 89cd53837b..084717c1b3 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -31,36 +31,43 @@ final class SubscriptionFlowViewModel: ObservableObject { let purchaseManager: PurchaseManager let viewTitle = UserText.settingsPProSection + enum Constants { + static let navigationBarHideThreshold = 40.0 + } + private var cancellables = Set() + private var canGoBackCancellable: AnyCancellable? // State variables var purchaseURL = URL.purchaseSubscription - // Closure passed to navigate to a specific section - // after returning to settings - var onFeatureSelected: ((SettingsViewModel.SettingsSection) -> Void) - enum FeatureName { static let netP = "vpn" - static let itp = "identity-theft-restoration" + static let itr = "identity-theft-restoration" static let dbp = "personal-information-removal" } // Published properties @Published var hasActiveSubscription = false @Published var transactionStatus: SubscriptionPagesUseSubscriptionFeature.TransactionStatus = .idle - @Published var shouldReloadWebView = false @Published var activatingSubscription = false @Published var shouldDismissView = false + @Published var webViewModel: AsyncHeadlessWebViewViewModel + @Published var shouldShowNavigationBar: Bool = false + @Published var selectedFeature: SettingsViewModel.SettingsSection? + @Published var canNavigateBack: Bool = false init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), purchaseManager: PurchaseManager = PurchaseManager.shared, - onFeatureSelected: @escaping ((SettingsViewModel.SettingsSection) -> Void)) { + selectedFeature: SettingsViewModel.SettingsSection? = nil) { self.userScript = userScript self.subFeature = subFeature self.purchaseManager = purchaseManager - self.onFeatureSelected = onFeatureSelected + self.selectedFeature = selectedFeature + self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, + subFeature: subFeature, + settings: AsyncHeadlessWebViewSettings(bounces: false)) } // Observe transaction status @@ -95,37 +102,71 @@ final class SubscriptionFlowViewModel: ObservableObject { .receive(on: DispatchQueue.main) .sink { [weak self] value in if value != nil { - self?.shouldDismissView = true switch value?.feature { case FeatureName.netP: - self?.onFeatureSelected(.netP) - case FeatureName.itp: - self?.onFeatureSelected(.itp) + self?.selectedFeature = .netP + case FeatureName.itr: + self?.selectedFeature = .itr case FeatureName.dbp: - self?.onFeatureSelected(.dbp) + self?.selectedFeature = .dbp default: - return + break } + self?.finalizeSubscriptionFlow() } + + } + .store(in: &cancellables) + + webViewModel.$scrollPosition + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold } .store(in: &cancellables) - + + canGoBackCancellable = webViewModel.$canGoBack + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.canNavigateBack = value + } } @MainActor private func setTransactionStatus(_ status: SubscriptionPagesUseSubscriptionFeature.TransactionStatus) { self.transactionStatus = status } + + @MainActor + private func disableGoBack() { + canGoBackCancellable?.cancel() + canNavigateBack = false + } func initializeViewData() async { await self.setupTransactionObserver() - await MainActor.run { shouldReloadWebView = true } + await self.updateSubscriptionStatus() + webViewModel.navigationCoordinator.navigateTo(url: purchaseURL ) } + func finalizeSubscriptionFlow() { + canGoBackCancellable?.cancel() + cancellables.removeAll() + subFeature.selectedFeature = nil + hasActiveSubscription = false + transactionStatus = .idle + activatingSubscription = false + shouldShowNavigationBar = false + selectedFeature = nil + canNavigateBack = false + shouldDismissView = true + } + func restoreAppstoreTransaction() { Task { if await subFeature.restoreAccountFromAppStorePurchase() { - await MainActor.run { shouldReloadWebView = true } + await disableGoBack() + await webViewModel.navigationCoordinator.reload() } else { await MainActor.run { } @@ -133,5 +174,17 @@ final class SubscriptionFlowViewModel: ObservableObject { } } + func updateSubscriptionStatus() async { + if AccountManager().isUserAuthenticated && hasActiveSubscription == false { + await disableGoBack() + await webViewModel.navigationCoordinator.reload() + } + } + + @MainActor + func navigateBack() async { + await webViewModel.navigationCoordinator.goBack() + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift new file mode 100644 index 0000000000..a076f08341 --- /dev/null +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -0,0 +1,144 @@ +// +// SubscriptionITPViewModel.swift +// DuckDuckGo +// +// Copyright © 2023 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 UserScript +import Combine +import Core + +#if SUBSCRIPTION +@available(iOS 15.0, *) +final class SubscriptionITPViewModel: ObservableObject { + + let userScript: IdentityTheftRestorationPagesUserScript + let subFeature: IdentityTheftRestorationPagesFeature + var manageITPURL = URL.manageITP + var viewTitle = UserText.settingsPProITRTitle + + enum Constants { + static let navigationBarHideThreshold = 40.0 + static let downloadableContent = ["application/pdf"] + } + + // State variables + var itpURL = URL.manageITP + @Published var webViewModel: AsyncHeadlessWebViewViewModel + @Published var shouldShowNavigationBar: Bool = false + @Published var canNavigateBack: Bool = false + @Published var isDownloadableContent: Bool = false + @Published var activityItems: [Any] = [] + @Published var attachmentURL: URL? + private var currentURL: URL? + + private var cancellables = Set() + private var canGoBackCancellable: AnyCancellable? + + init(userScript: IdentityTheftRestorationPagesUserScript = IdentityTheftRestorationPagesUserScript(), + subFeature: IdentityTheftRestorationPagesFeature = IdentityTheftRestorationPagesFeature()) { + self.userScript = userScript + self.subFeature = subFeature + self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, + subFeature: subFeature, + settings: AsyncHeadlessWebViewSettings(bounces: false)) + } + + // Observe transaction status + private func setupSubscribers() async { + + webViewModel.$scrollPosition + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold + } + .store(in: &cancellables) + + webViewModel.$contentType + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + guard let strongSelf = self else { return } + + if Constants.downloadableContent.contains(value) { + strongSelf.isDownloadableContent = true + guard let url = strongSelf.currentURL else { return } + Task { + // We are using a dummy PDF for testing, as the real PDF's are behind the internal user login + if let downloadURL = URL(string: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf") { + await strongSelf.downloadAttachment(from: downloadURL) + } + // if let downloadURL = url { + // await strongSelf.downloadAttachment(from: downloadURL) + } + } + } + .store(in: &cancellables) + + webViewModel.$url + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.isDownloadableContent = false + self?.currentURL = value + } + .store(in: &cancellables) + + + canGoBackCancellable = webViewModel.$canGoBack + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.canNavigateBack = value + } + } + + func initializeView() { + webViewModel.navigationCoordinator.navigateTo(url: manageITPURL ) + Task { await setupSubscribers() } + } + + private func downloadAttachment(from url: URL) async { + if let (temporaryURL, _) = try? await URLSession.shared.download(from: url) { + let fileManager = FileManager.default + + let fileName = url.lastPathComponent + + let tempDirectory = fileManager.temporaryDirectory + let tempFileURL = tempDirectory.appendingPathComponent(fileName) + + if fileManager.fileExists(atPath: tempFileURL.path) { + try? fileManager.removeItem(at: tempFileURL) + } + try? fileManager.moveItem(at: temporaryURL, to: tempFileURL) + DispatchQueue.main.async { + self.attachmentURL = tempFileURL + } + } + } + + + @MainActor + private func disableGoBack() { + canGoBackCancellable?.cancel() + canNavigateBack = false + } + + @MainActor + func navigateBack() async { + await webViewModel.navigationCoordinator.goBack() + } + +} +#endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 458be8fad4..0733cd2e83 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -39,7 +39,6 @@ final class SubscriptionRestoreViewModel: ObservableObject { @Published var transactionStatus: SubscriptionPagesUseSubscriptionFeature.TransactionStatus = .idle @Published var activationResult: SubscriptionActivationResult = .unknown @Published var subscriptionEmail: String? - @Published var isManagingEmailSubscription: Bool = false init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), @@ -80,9 +79,5 @@ final class SubscriptionRestoreViewModel: ObservableObject { } } - func manageEmailSubscription() { - isManagingEmailSubscription = true - } - } #endif diff --git a/DuckDuckGo/Subscription/Views/HeadlessWebView.swift b/DuckDuckGo/Subscription/Views/HeadlessWebView.swift deleted file mode 100644 index 116e34f771..0000000000 --- a/DuckDuckGo/Subscription/Views/HeadlessWebView.swift +++ /dev/null @@ -1,131 +0,0 @@ -// -// HeadlessWebView.swift -// DuckDuckGo -// -// Copyright © 2023 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 WebKit -import UserScript -import SwiftUI -import DesignResourcesKit -import Core - -struct HeadlessWebview: UIViewRepresentable { - let userScript: UserScriptMessaging - let subFeature: Subfeature - @Binding var url: URL - @Binding var shouldReload: Bool - - func makeUIView(context: Context) -> WKWebView { - let configuration = WKWebViewConfiguration() - configuration.userContentController = makeUserContentController() - - let webView = WKWebView(frame: .zero, configuration: configuration) - DefaultUserAgentManager.shared.update(webView: webView, isDesktop: false, url: url) - - // Just add time if you need to hook the WebView inspector - DispatchQueue.main.asyncAfter(deadline: .now() + 0) { - webView.load(URLRequest(url: url)) - } - - webView.uiDelegate = context.coordinator - - -#if DEBUG - if #available(iOS 16.4, *) { - webView.isInspectable = true - } -#endif - return webView - } - - func updateUIView(_ uiView: WKWebView, context: Context) { - if shouldReload { - reloadView(uiView: uiView) - } - } - - @MainActor - func reloadView(uiView: WKWebView) { - uiView.reload() - DispatchQueue.main.async { - shouldReload = false - } - } - - func makeCoordinator() -> Coordinator { - Coordinator() - } - - @MainActor - private func makeUserContentController() -> WKUserContentController { - let userContentController = WKUserContentController() - userContentController.addUserScript(userScript.makeWKUserScriptSync()) - userContentController.addHandler(userScript) - userScript.registerSubfeature(delegate: subFeature) - return userContentController - } - - class Coordinator: NSObject, WKUIDelegate { - var webView: WKWebView? - - private func topMostViewController() -> UIViewController? { - var topController: UIViewController? = UIApplication.shared.windows.filter { $0.isKeyWindow } - .first? - .rootViewController - while let presentedViewController = topController?.presentedViewController { - topController = presentedViewController - } - return topController - } - - // MARK: WKUIDelegate - - // Enables presenting Javascript alerts via the native layer (window.confirm()) - func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, - initiatedByFrame frame: WKFrameInfo, - completionHandler: @escaping (Bool) -> Void) { - let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: UserText.actionCancel, style: .cancel, handler: { _ in completionHandler(false) })) - alertController.addAction(UIAlertAction(title: UserText.actionOK, style: .default, handler: { _ in completionHandler(true) })) - - if let topController = topMostViewController() { - topController.present(alertController, animated: true, completion: nil) - } else { - completionHandler(false) - } - } - } -} - -struct AsyncHeadlessWebView: View { - @Binding var url: URL - let userScript: UserScriptMessaging - let subFeature: Subfeature - @Binding var shouldReload: Bool - - var body: some View { - GeometryReader { geometry in - HeadlessWebview(userScript: userScript, - subFeature: subFeature, - url: $url, - shouldReload: $shouldReload) - .frame(width: geometry.size.width, height: geometry.size.height) - } - } - -} diff --git a/DuckDuckGo/Subscription/Views/RootPresentationMode.swift b/DuckDuckGo/Subscription/Views/RootPresentationMode.swift new file mode 100644 index 0000000000..e43fc4bc08 --- /dev/null +++ b/DuckDuckGo/Subscription/Views/RootPresentationMode.swift @@ -0,0 +1,45 @@ +// +// RootPresentationMode.swift +// DuckDuckGo +// +// 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 + +/* + iOS15 does not support NavigationStack navigation so this creates a 'RootPresentationMode' + environment that views can use to create a binding for dismissal of the whole stack of views + See: https://stackoverflow.com/questions/57334455/how-can-i-pop-to-the-root-view-using-swiftui + */ +struct RootPresentationModeKey: EnvironmentKey { + static let defaultValue: Binding = .constant(RootPresentationMode()) +} + +extension EnvironmentValues { + var rootPresentationMode: Binding { + get { return self[RootPresentationModeKey.self] } + set { self[RootPresentationModeKey.self] = newValue } + } +} + +typealias RootPresentationMode = Bool + +extension RootPresentationMode { + + public mutating func dismiss() { + self.toggle() + } +} diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 714e9696ea..50811f81a6 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -24,34 +24,35 @@ import Foundation @available(iOS 15.0, *) struct SubscriptionEmailView: View { - @ObservedObject var viewModel: SubscriptionEmailViewModel - @Binding var isActivatingSubscription: Bool + @StateObject var viewModel = SubscriptionEmailViewModel() @Environment(\.dismiss) var dismiss + @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding + @State private var isActive: Bool = false + @State var isAddingDevice = false var body: some View { ZStack { VStack { - AsyncHeadlessWebView(url: $viewModel.emailURL, - userScript: viewModel.userScript, - subFeature: viewModel.subFeature, - shouldReload: $viewModel.shouldReloadWebView).background() + AsyncHeadlessWebView(viewModel: viewModel.webViewModel) + .background() } } + .onAppear { + viewModel.loadURL() + } + .onChange(of: viewModel.activateSubscription) { active in if active { - // We just need to dismiss the current view - if viewModel.managingSubscriptionEmail { + // If updating email, just go back + if isAddingDevice { dismiss() } else { - // Update the binding to tear down the entire view stack - // This dismisses all views in between and takes you back to the welcome page - isActivatingSubscription = false + // Pop to Root view + self.rootPresentationMode.wrappedValue.dismiss() } } } .navigationTitle(viewModel.viewTitle) } - - } #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index e9b06406ac..d8a2064a8d 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -20,13 +20,72 @@ #if SUBSCRIPTION import SwiftUI import Foundation +import DesignResourcesKit @available(iOS 15.0, *) struct SubscriptionFlowView: View { - + @Environment(\.dismiss) var dismiss - @ObservedObject var viewModel: SubscriptionFlowViewModel + @StateObject var viewModel = SubscriptionFlowViewModel() @State private var isAlertVisible = false + @State private var shouldShowNavigationBar = false + @State private var isActive: Bool = false + + enum Constants { + static let daxLogo = "Home" + static let daxLogoSize: CGFloat = 24.0 + static let empty = "" + static let navButtonPadding: CGFloat = 20.0 + static let backButtonImage = "chevron.left" + } + + var body: some View { + NavigationView { + baseView + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + backButton + } + ToolbarItem(placement: .principal) { + HStack { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(viewModel.viewTitle).daxBodyRegular() + } + } + } + .edgesIgnoringSafeArea(.top) + .navigationBarTitleDisplayMode(.inline) + .navigationBarHidden(!viewModel.shouldShowNavigationBar).animation(.easeOut) + } + .tint(Color(designSystemColor: .textPrimary)) + .environment(\.rootPresentationMode, self.$isActive) + } + + @ViewBuilder + private var dismissButton: some View { + Button(action: { viewModel.finalizeSubscriptionFlow() }, label: { Text(UserText.subscriptionCloseButton) }) + .padding(Constants.navButtonPadding) + .contentShape(Rectangle()) + .tint(Color(designSystemColor: .textPrimary)) + } + + @ViewBuilder + private var backButton: some View { + if viewModel.canNavigateBack { + Button(action: { + Task { await viewModel.navigateBack() } + }, label: { + HStack(spacing: 0) { + Image(systemName: Constants.backButtonImage) + Text(UserText.backButtonTitle) + } + + }) + } + } private func getTransactionStatus() -> String { switch viewModel.transactionStatus { @@ -41,48 +100,49 @@ struct SubscriptionFlowView: View { } } - var body: some View { - ZStack { - AsyncHeadlessWebView(url: $viewModel.purchaseURL, - userScript: viewModel.userScript, - subFeature: viewModel.subFeature, - shouldReload: $viewModel.shouldReloadWebView).background() - - // Overlay that appears when transaction is in progress - if viewModel.transactionStatus != .idle { - PurchaseInProgressView(status: getTransactionStatus()) - } - - // Activation View - NavigationLink(destination: SubscriptionRestoreView(viewModel: SubscriptionRestoreViewModel(), - isActivatingSubscription: $viewModel.activatingSubscription), - isActive: $viewModel.activatingSubscription) { - EmptyView() - } - } - .onChange(of: viewModel.shouldReloadWebView) { shouldReload in - if shouldReload { - viewModel.shouldReloadWebView = false + + @ViewBuilder + private var baseView: some View { + ZStack(alignment: .top) { + webView + + // Show a dismiss button while the bar is not visible + // But it should be hidden while performing a transaction + if !shouldShowNavigationBar && viewModel.transactionStatus == .idle { + HStack { + backButton.padding(.leading, Constants.navButtonPadding) + Spacer() + dismissButton + } } } + .onChange(of: viewModel.hasActiveSubscription) { result in if result { isAlertVisible = true } } + .onChange(of: viewModel.shouldDismissView) { result in if result { dismiss() + viewModel.shouldDismissView = false + } + } + + .onChange(of: viewModel.activatingSubscription) { value in + if value { + isActive = true + viewModel.activatingSubscription = false } } .onAppear(perform: { + setUpAppearances() Task { await viewModel.initializeViewData() } + }) - .navigationTitle(viewModel.viewTitle) - .navigationBarBackButtonHidden(viewModel.transactionStatus != .idle) - // Active subscription found Alert .alert(isPresented: $isAlertVisible) { Alert( title: Text(UserText.subscriptionFoundTitle), @@ -94,7 +154,38 @@ struct SubscriptionFlowView: View { } ) } - .navigationBarBackButtonHidden(viewModel.transactionStatus != .idle) + // The trailing close button should be hidden when a transaction is in progress + .navigationBarItems(trailing: viewModel.transactionStatus == .idle + ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } + : nil) } + + @ViewBuilder + private var webView: some View { + + ZStack(alignment: .top) { + // Restore View Hidden Link + NavigationLink(destination: SubscriptionRestoreView(), isActive: $isActive) { + EmptyView() + }.isDetailLink(false) + + AsyncHeadlessWebView(viewModel: viewModel.webViewModel) + .background() + + if viewModel.transactionStatus != .idle { + PurchaseInProgressView(status: getTransactionStatus()) + } + + } + } + + private func setUpAppearances() { + let navAppearance = UINavigationBar.appearance() + navAppearance.backgroundColor = UIColor(designSystemColor: .surface) + navAppearance.barTintColor = UIColor(designSystemColor: .surface) + navAppearance.shadowImage = UIImage() + navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) + } + } #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift new file mode 100644 index 0000000000..7d8c83c7b6 --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -0,0 +1,155 @@ +// +// SubscriptionITPView.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if SUBSCRIPTION +import SwiftUI +import Foundation +import DesignResourcesKit + +struct SubscriptionActivityViewController: UIViewControllerRepresentable { + var activityItems: [Any] + var applicationActivities: [UIActivity]? + + func makeUIViewController(context: Context) -> UIActivityViewController { + return UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities) + } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} +} + +@available(iOS 15.0, *) +struct SubscriptionITPView: View { + + @Environment(\.dismiss) var dismiss + @StateObject var viewModel = SubscriptionITPViewModel() + @State private var shouldShowNavigationBar = false + @State private var isShowingActivityView = false + + enum Constants { + static let daxLogo = "Home" + static let daxLogoSize: CGFloat = 24.0 + static let empty = "" + static let navButtonPadding: CGFloat = 20.0 + static let backButtonImage = "chevron.left" + static let shareImage = "SubscriptionShareIcon" + } + + var body: some View { + NavigationView { + baseView + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + backButton + } + ToolbarItem(placement: .principal) { + HStack { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(viewModel.viewTitle).daxBodyRegular() + } + } + ToolbarItem(placement: .navigationBarTrailing) { + shareButton + } + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.subscriptionCloseButton) { dismiss() } + } + } + .edgesIgnoringSafeArea(.top) + .navigationBarTitleDisplayMode(.inline) + .navigationBarHidden(!viewModel.shouldShowNavigationBar && !viewModel.isDownloadableContent).animation(.easeOut) + + .onAppear(perform: { + setUpAppearances() + viewModel.initializeView() + }) + }.tint(Color(designSystemColor: .textPrimary)) + } + + private var baseView: some View { + ZStack(alignment: .top) { + webView + + // Show a dismiss button while the bar is not visible + // But it should be hidden while performing a transaction + if !shouldShowNavigationBar { + HStack { + backButton.padding(.leading, Constants.navButtonPadding) + Spacer() + dismissButton + } + } + + } + } + + @ViewBuilder + private var webView: some View { + + ZStack(alignment: .top) { + AsyncHeadlessWebView(viewModel: viewModel.webViewModel) + .background() + } + } + + @ViewBuilder + private var backButton: some View { + if viewModel.canNavigateBack { + Button(action: { + Task { await viewModel.navigateBack() } + }, label: { + HStack(spacing: 0) { + Image(systemName: Constants.backButtonImage) + Text(UserText.backButtonTitle) + } + + }) + } + } + + @ViewBuilder + private var shareButton: some View { + if viewModel.isDownloadableContent { + Button(action: { isShowingActivityView = true }, label: { Image(Constants.shareImage) }) + .popover(isPresented: $isShowingActivityView, arrowEdge: .bottom) { + SubscriptionActivityViewController(activityItems: [viewModel.attachmentURL], applicationActivities: nil) + } + } + } + + @ViewBuilder + private var dismissButton: some View { + Button(action: { dismiss() }, label: { Text(UserText.subscriptionCloseButton) }) + .padding(Constants.navButtonPadding) + .contentShape(Rectangle()) + .tint(Color(designSystemColor: .textPrimary)) + } + + + private func setUpAppearances() { + let navAppearance = UINavigationBar.appearance() + navAppearance.backgroundColor = UIColor(designSystemColor: .surface) + navAppearance.barTintColor = UIColor(designSystemColor: .surface) + navAppearance.shadowImage = UIImage() + navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) + } +} +#endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 3006cf4ace..2301cf7daa 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -26,15 +26,14 @@ import DesignResourcesKit struct SubscriptionRestoreView: View { @Environment(\.dismiss) var dismiss - @StateObject var viewModel: SubscriptionRestoreViewModel + @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding + @StateObject var viewModel = SubscriptionRestoreViewModel() @State private var expandedItemId: Int = 0 @State private var isAlertVisible = false - - // Binding used to dismiss the entire stack (Go back to settings from several levels down) - @Binding var isActivatingSubscription: Bool + @State private var isActive: Bool = false private enum Constants { - static let heroImage = "SyncTurnOnSyncHero" + static let heroImage = "ManageSubscriptionHero" static let appleIDIcon = "Platform-Apple-16" static let emailIcon = "Email-16" static let headerLineSpacing = 10.0 @@ -50,6 +49,12 @@ struct SubscriptionRestoreView: View { var body: some View { ZStack { VStack { + + // Email Activation View Hidden link + NavigationLink(destination: SubscriptionEmailView(isAddingDevice: viewModel.isAddingDevice), isActive: $isActive) { + EmptyView() + }.isDetailLink(false) + headerView listView } @@ -58,6 +63,7 @@ struct SubscriptionRestoreView: View { .navigationBarBackButtonHidden(viewModel.transactionStatus != .idle) .applyInsetGroupedListStyle() .alert(isPresented: $isAlertVisible) { getAlert() } + .onChange(of: viewModel.activationResult) { result in if result != .unknown { isAlertVisible = true @@ -72,16 +78,6 @@ struct SubscriptionRestoreView: View { } } - // Activation View - NavigationLink(destination: SubscriptionEmailView( - viewModel: SubscriptionEmailViewModel( - userScript: viewModel.userScript, - subFeature: viewModel.subFeature, - accountManager: viewModel.accountManager), - isActivatingSubscription: $isActivatingSubscription), - isActive: $viewModel.isManagingEmailSubscription) { - EmptyView() - } } private var listItems: [ListItem] { @@ -93,7 +89,7 @@ struct SubscriptionRestoreView: View { .init(id: 1, content: getCellTitle(icon: Constants.emailIcon, text: UserText.subscriptionActivateEmail), - expandedContent: getEmailCellContent(buttonAction: viewModel.manageEmailSubscription )) + expandedContent: getEmailCellContent(buttonAction: { isActive = true })) ] } @@ -138,12 +134,6 @@ struct SubscriptionRestoreView: View { HStack { getCellButton(buttonText: UserText.subscriptionManageEmailButton, action: buttonAction) - /* TO BE IMPLEMENTED ?? - Spacer() - Button(action: {}, label: { - Text(UserText.subscriptionManageEmailResendInstructions).daxButton().daxBodyBold() - }) - */ } } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index 8e44ce451c..49b2b76aa3 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -29,10 +29,9 @@ class SceneEnvironment: ObservableObject { @available(iOS 15.0, *) struct SubscriptionSettingsView: View { - @ObservedObject var viewModel: SubscriptionSettingsViewModel @Environment(\.presentationMode) var presentationMode + @StateObject var viewModel = SubscriptionSettingsViewModel() @StateObject var sceneEnvironment = SceneEnvironment() - @State private var isActivatingSubscription = false var body: some View { List { @@ -46,9 +45,8 @@ struct SubscriptionSettingsView: View { }.textCase(nil) Section(header: Text(UserText.subscriptionManageDevices)) { - NavigationLink(destination: SubscriptionRestoreView( - viewModel: SubscriptionRestoreViewModel(isAddingDevice: true), - isActivatingSubscription: $isActivatingSubscription)) { + + NavigationLink(destination: SubscriptionRestoreView()) { SettingsCustomCell(content: { Text(UserText.subscriptionAddDeviceButton) .daxBodyRegular() @@ -56,6 +54,7 @@ struct SubscriptionSettingsView: View { }) } + SettingsCustomCell(content: { Text(UserText.subscriptionRemoveFromDevice) .daxBodyRegular() diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 5b9b476aac..9c24c77556 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1033,6 +1033,9 @@ But if you *do* want a peek under the hood, you can find more information about static let subscriptionCompletingPurchaseTitle = NSLocalizedString("subscription.progress.view.completing.purchase", value: "Completing purchase...", comment: "Progress view title when completing the purchase") // Subscription Settings + public static let subscriptionTitle = NSLocalizedString("subscription.title", value: "Privacy Pro", comment: "Navigation bar Title for subscriptions") + public static let subscriptionCloseButton = NSLocalizedString("subscription.close", value: "Close", comment: "Navigation Button for closing subscription view") + static func subscriptionInfo(expiration: String) -> String { let localized = NSLocalizedString("subscription.subscription.active.caption", value: "Your Privacy Pro subscription renews on %@", comment: "Subscription Expiration Data") return String(format: localized, expiration) diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 19241e7e48..69faa3d347 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1980,6 +1980,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Change plan or billing title */ "subscription.change.plan" = "Change Plan Or Billing"; +/* Navigation Button for closing subscription view */ +"subscription.close" = "Close"; + /* FAQ Button */ "subscription.faq" = "Privacy Pro FAQ"; @@ -2055,6 +2058,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Subscription Expiration Data */ "subscription.subscription.active.caption" = "Your Privacy Pro subscription renews on %@"; +/* Navigation bar Title for subscriptions */ +"subscription.title" = "Privacy Pro"; + /* Message confirming that recovery code was copied to clipboard */ "sync.code.copied" = "Recovery code copied to clipboard"; From 5b7ae97859fbc4a27799c32ab22cc34158484142 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 9 Feb 2024 16:08:50 +0100 Subject: [PATCH 023/245] Fix hotfixing process (#2443) --- scripts/prepare_release.sh | 61 ++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index d413781f84..a262bf1255 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -3,7 +3,7 @@ set -eo pipefail mute=">/dev/null 2>&1" -release_branch_parent="main" +base_branch="main" build_number=0 # Get the directory where the script is stored @@ -64,6 +64,12 @@ print_usage_and_exit() { die "${reason}" } +stash() { + printf '%s' "Stashing your changes ... " + eval git stash "$mute" + echo "✅" +} + read_command_line_arguments() { local input="$1" local version_regexp="^[0-9]+(\.[0-9]+)*$" @@ -92,29 +98,36 @@ read_command_line_arguments() { done } -process_release() { +process_release() { # expected input e.g. "1.72.0" version="$1" release_branch="release/${version}" echo "Processing version number: $version" if release_branch_exists; then - is_subsequent_release=1 + is_subsequent_release=1 + base_branch="$release_branch" fi } -process_hotfix() { - local input="$1" - echo "Processing hotfix branch name: $input" - +process_hotfix() { # expected input e.g. "hotfix/1.72.1" + version=$(echo "$1" | cut -d '/' -f 2) + release_branch="$1" + base_branch="$1" is_hotfix=1 - release_branch="$input" + + echo "Processing hotfix branch name: $release_branch" if ! release_branch_exists; then - die "💥 Error: Hotfix branch ${release_branch} does not exist" + die "💥 Error: Hotfix branch ${release_branch} does not exist. It should be created before you run this script." fi } +checkout_base_branch() { + eval git checkout "${base_branch}" "$mute" + eval git pull "$mute" +} + release_branch_exists() { if git show-ref --verify --quiet "refs/heads/${release_branch}"; then return 0 @@ -123,21 +136,11 @@ release_branch_exists() { fi } -stash() { - printf '%s' "Stashing your changes ... " - eval git stash "$mute" - echo "✅" -} - create_release_branch() { printf '%s' "Creating release branch ... " - eval git checkout "${release_branch_parent}" "$mute" - eval git pull "$mute" - if [[ ! $is_subsequent_release && ! $is_hotfix ]]; then - if git show-ref --quiet "refs/heads/${release_branch}"; then - die "💥 Error: Branch ${release_branch} already exists" - fi + if git show-ref --quiet "refs/heads/${release_branch}"; then + die "💥 Error: Branch ${release_branch} already exists" fi eval git checkout -b "${release_branch}" "$mute" @@ -147,17 +150,10 @@ create_release_branch() { create_build_branch() { printf '%s' "Creating build branch ... " - eval git checkout "${release_branch}" "$mute" - eval git pull "$mute" local temp_file local latest_build_number - if [[ $is_hotfix ]]; then - version=$(cut -d' ' -f3 < "${base_dir}/Configuration/Version.xcconfig") - version=$(bump_patch_number "$version") - fi - temp_file=$(mktemp) bundle exec fastlane latest_build_number_for_version version:"$version" file_name:"$temp_file" latest_build_number="$(<"$temp_file")" @@ -183,12 +179,6 @@ update_marketing_version() { echo "✅" } -bump_patch_number() { - IFS='.' read -ra arrIN <<< "$1" - local patch_number=$((arrIN[2] + 1)) - echo "${arrIN[0]}.${arrIN[1]}.$patch_number" -} - update_build_version() { echo "Setting build version ..." (cd "$base_dir" && bundle exec fastlane increment_build_number_for_version version:"${version}") @@ -243,8 +233,9 @@ main() { assert_fastlane_installed assert_gh_installed_and_authenticated - read_command_line_arguments "$@" stash + read_command_line_arguments "$@" + checkout_base_branch if [[ $is_subsequent_release ]]; then create_build_branch From e3c0b6cbed27f35907cb835baa02853df69f17a6 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 9 Feb 2024 07:35:28 -0800 Subject: [PATCH 024/245] Fix HomeViewController retain cycle (#2462) Task/Issue URL: https://app.asana.com/0/414235014887631/1206561582798187/f Tech Design URL: CC: Description: This PR fixes an issue with every HomeViewController instance being affected by a retain cycle. --- DuckDuckGo/HomeCollectionView.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/HomeCollectionView.swift b/DuckDuckGo/HomeCollectionView.swift index 7d3a086e82..54a216ef30 100644 --- a/DuckDuckGo/HomeCollectionView.swift +++ b/DuckDuckGo/HomeCollectionView.swift @@ -85,8 +85,12 @@ class HomeCollectionView: UICollectionView { case .favorites: let renderer = FavoritesHomeViewSectionRenderer(viewModel: favoritesViewModel) - renderer.onFaviconMissing = { _ in - controller.faviconsFetcherOnboarding.presentOnboardingIfNeeded(from: controller) + renderer.onFaviconMissing = { [weak self] _ in + guard let self else { + return + } + + self.controller.faviconsFetcherOnboarding.presentOnboardingIfNeeded(from: self.controller) } renderers.install(renderer: renderer) From 3aa5f6fcaa792449f348f482d552aebf8293897a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Feb 2024 18:42:26 +0100 Subject: [PATCH 025/245] Bump submodules/privacy-reference-tests from `a3acc21` to `6b7ad1e` (#2460) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- submodules/privacy-reference-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index a3acc21947..6b7ad1e7f1 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit a3acc2194758bec0f01f57dd0c5f106de01a354e +Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 From 07df3a079df28122521064ee547b42789c04c8d0 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Sun, 11 Feb 2024 18:51:47 -0800 Subject: [PATCH 026/245] Add debug command to disable VPN (#2391) Task/Issue URL: https://app.asana.com/0/414235014887631/1206448305134854/f Tech Design URL: CC: Description: This PR adds a "Disable VPN" command to the debug menu, which can disable Connect on Demand and disable the VPN from within the extension. This is so that we can test that this iOS 17 change really works, before beginning to rely on it. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- .../NetworkProtectionDebugUtilities.swift | 10 ++++++ ...NetworkProtectionDebugViewController.swift | 33 +++++++++++-------- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 7 files changed, 36 insertions(+), 19 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8a62e8e494..b16297a76b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10005,7 +10005,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 105.0.0; + version = 106.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7b5e6890b5..1ba6be112c 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "c14f36805fc5c4c8ec68198fc1ce7e0e0a97e233", - "version" : "105.0.0" + "revision" : "f9cf9ae340e918042d59c128bf83d0924e5d4e85", + "version" : "106.0.0" } }, { diff --git a/DuckDuckGo/NetworkProtectionDebugUtilities.swift b/DuckDuckGo/NetworkProtectionDebugUtilities.swift index 822d261b61..ebbc96ea39 100644 --- a/DuckDuckGo/NetworkProtectionDebugUtilities.swift +++ b/DuckDuckGo/NetworkProtectionDebugUtilities.swift @@ -48,6 +48,16 @@ final class NetworkProtectionDebugUtilities { try? await activeSession.sendProviderMessage(.triggerTestNotification) } + // MARK: - Disable VPN + + func disableConnectOnDemandAndShutDown() async { + guard let activeSession = try? await ConnectionSessionUtilities.activeSession() else { + return + } + + try? await activeSession.sendProviderMessage(.request(.debugCommand(.disableConnectOnDemandAndShutDown))) + } + // MARK: - Failure Simulation func triggerSimulation(_ option: NetworkProtectionSimulationOption) async { diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 55c4445f64..73f2453a99 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -39,9 +39,9 @@ final class NetworkProtectionDebugViewController: UITableViewController { private let titles = [ Sections.clearData: "Clear Data", Sections.debugFeature: "Debug Features", + Sections.debugCommand: "Debug Commands", Sections.simulateFailure: "Simulate Failure", Sections.registrationKey: "Registration Key", - Sections.notifications: "Notifications", Sections.networkPath: "Network Path", Sections.lastDisconnectError: "Last Disconnect Error", Sections.connectionTest: "Connection Test", @@ -52,9 +52,9 @@ final class NetworkProtectionDebugViewController: UITableViewController { enum Sections: Int, CaseIterable { case clearData case debugFeature + case debugCommand case simulateFailure case registrationKey - case notifications case connectionTest case networkPath case lastDisconnectError @@ -84,8 +84,9 @@ final class NetworkProtectionDebugViewController: UITableViewController { case expireNow } - enum NotificationsRows: Int, CaseIterable { + enum ExtensionDebugCommandRows: Int, CaseIterable { case triggerTestNotification + case shutDown } enum NetworkPathRows: Int, CaseIterable { @@ -184,15 +185,15 @@ final class NetworkProtectionDebugViewController: UITableViewController { case .debugFeature: configure(cell, forDebugFeatureAtRow: indexPath.row) + case .debugCommand: + configure(cell, forNotificationRow: indexPath.row) + case .simulateFailure: configure(cell, forSimulateFailureAtRow: indexPath.row) case .registrationKey: configure(cell, forRegistrationKeyRow: indexPath.row) - case .notifications: - configure(cell, forNotificationRow: indexPath.row) - case .networkPath: configure(cell, forNetworkPathRow: indexPath.row) @@ -216,9 +217,9 @@ final class NetworkProtectionDebugViewController: UITableViewController { switch Sections(rawValue: section) { case .clearData: return ClearDataRows.allCases.count case .debugFeature: return DebugFeatureRows.allCases.count + case .debugCommand: return ExtensionDebugCommandRows.allCases.count case .simulateFailure: return SimulateFailureRows.allCases.count case .registrationKey: return RegistrationKeyRows.allCases.count - case .notifications: return NotificationsRows.allCases.count case .networkPath: return NetworkPathRows.allCases.count case .lastDisconnectError: return LastDisconnectErrorRows.allCases.count case .connectionTest: return ConnectionTestRows.allCases.count + connectionTestResults.count @@ -239,12 +240,12 @@ final class NetworkProtectionDebugViewController: UITableViewController { } case .debugFeature: didSelectDebugFeature(at: indexPath) + case .debugCommand: + didSelectDebugCommand(at: indexPath) case .simulateFailure: didSelectSimulateFailure(at: indexPath) case .registrationKey: didSelectRegistrationKeyAction(at: indexPath) - case .notifications: - didSelectTestNotificationAction(at: indexPath) case .networkPath: break case .lastDisconnectError: @@ -355,23 +356,29 @@ final class NetworkProtectionDebugViewController: UITableViewController { } } - // MARK: Notifications + // MARK: VPN Extension Debug Commands private func configure(_ cell: UITableViewCell, forNotificationRow row: Int) { - switch NotificationsRows(rawValue: row) { + switch ExtensionDebugCommandRows(rawValue: row) { case .triggerTestNotification: cell.textLabel?.text = "Test Notification" + case .shutDown: + cell.textLabel?.text = "Disable VPN From Extension" case .none: break } } - private func didSelectTestNotificationAction(at indexPath: IndexPath) { - switch NotificationsRows(rawValue: indexPath.row) { + private func didSelectDebugCommand(at indexPath: IndexPath) { + switch ExtensionDebugCommandRows(rawValue: indexPath.row) { case .triggerTestNotification: Task { try await NetworkProtectionDebugUtilities().sendTestNotificationRequest() } + case .shutDown: + Task { + await NetworkProtectionDebugUtilities().disableConnectOnDemandAndShutDown() + } case .none: break } diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 2c64017fc7..0e8afdefb8 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "105.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index e984890cbb..873f398e8b 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "105.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 2d43130bc5..c299bcce08 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "105.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 61d31449f3ea0edbe9440d413a7aec98b99310e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Mon, 12 Feb 2024 21:46:58 +0100 Subject: [PATCH 027/245] Release 7.109.0-0 (#2470) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/AppTrackerDataSetProvider.swift | 4 +- Core/ios-config.json | 123 +- Core/trackerData.json | 2211 +++++++++++------ DuckDuckGo.xcodeproj/project.pbxproj | 56 +- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 7 files changed, 1633 insertions(+), 769 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 6c153b1550..26ca639588 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.108.0 +MARKETING_VERSION = 7.109.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index aad4388e47..f97aecbb98 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"4796cc720aa6849f3a8d27610ab20aac\"" - public static let embeddedDataSHA = "1fb091f103d9b382cd1bc1bc1577591e63c8e11f7b2c3b52c2f5392b992295e2" + public static let embeddedDataETag = "\"c3bb1ebaa170bfc3543945de56cbc25e\"" + public static let embeddedDataSHA = "576368fbd8b9edd69b352fdf05250beb1f942d2eb00df3ff090b14a399ee5df0" } public var embeddedDataEtag: String { diff --git a/Core/AppTrackerDataSetProvider.swift b/Core/AppTrackerDataSetProvider.swift index 6b1e7bfc49..e4041d3cad 100644 --- a/Core/AppTrackerDataSetProvider.swift +++ b/Core/AppTrackerDataSetProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppTrackerDataSetProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"144361b3801e3d4c33c5aff8d8de3c6b\"" - public static let embeddedDataSHA = "0cf5a43c234d54c3168cc28a65c19b0c5804c15e87aae3e8368d2b2f775a1a8b" + public static let embeddedDataETag = "\"0b6a7a2629abc170a505b92aebd67017\"" + public static let embeddedDataSHA = "32cd805f6be415e77affdf51929494c7add6363234cef58ea8b53ca3a08c86d4" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index 10c4861e9d..b13bcf949e 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1706872224316, + "version": 1707768517000, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -257,9 +257,6 @@ { "domain": "youtube.com" }, - { - "domain": "forbes.com" - }, { "domain": "tuc.org.uk" }, @@ -286,12 +283,27 @@ ] }, "state": "enabled", - "hash": "b082ab4a63d22eb4eafee199c52b1c61" + "features": { + "onByDefault": { + "state": "enabled", + "rollout": { + "steps": [ + { + "percent": 25 + } + ] + } + } + }, + "hash": "d01ba9e47542c11b7dd4bf9d5f25d16b" }, "autofill": { "exceptions": [ { "domain": "roll20.net" + }, + { + "domain": "rumble.com" } ], "state": "enabled", @@ -334,7 +346,7 @@ } } }, - "hash": "bd604dcd1f7bb584185f0c1cb94a5771" + "hash": "ffaa2e81fb2bf264cb5ce2dadac549e1" }, "clickToLoad": { "exceptions": [ @@ -1707,6 +1719,7 @@ "close ad", "close this ad", "x", + "_", "sponsored", "sponsorisé", "story continues below advertisement", @@ -2184,6 +2197,15 @@ } ] }, + { + "domain": "elpais.com", + "rules": [ + { + "selector": ".obne_ob", + "type": "hide-empty" + } + ] + }, { "domain": "eniro.se", "rules": [ @@ -2337,6 +2359,14 @@ { "selector": "fbs-ad", "type": "closest-empty" + }, + { + "selector": ".top-ad-container", + "type": "hide" + }, + { + "selector": ".vestpocket", + "type": "hide-empty" } ] }, @@ -2637,6 +2667,19 @@ } ] }, + { + "domain": "ieee.org", + "rules": [ + { + "selector": ".top-leader-container", + "type": "hide-empty" + }, + { + "selector": "[class*='rblad-ieee_']", + "type": "hide-empty" + } + ] + }, { "domain": "ilfattoquotidiano.it", "rules": [ @@ -2779,6 +2822,24 @@ } ] }, + { + "domain": "livescore.com", + "rules": [ + { + "selector": "#header-ads-holder", + "type": "hide-empty" + } + ] + }, + { + "domain": "livesport.com", + "rules": [ + { + "selector": "#header-ads-holder", + "type": "hide-empty" + } + ] + }, { "domain": "macrumors.com", "rules": [ @@ -2892,6 +2953,27 @@ } ] }, + { + "domain": "newatlas.com", + "rules": [ + { + "selector": ".OcelotAdModule", + "type": "hide-empty" + }, + { + "selector": "[id*='desktop_article_']", + "type": "hide-empty" + }, + { + "selector": "[id*='mobile_article_']", + "type": "hide-empty" + }, + { + "selector": "[id*='native_index_desktop_']", + "type": "hide-empty" + } + ] + }, { "domain": "newser.com", "rules": [ @@ -3142,6 +3224,19 @@ } ] }, + { + "domain": "primagames.com", + "rules": [ + { + "selector": ".primis-player", + "type": "hide-empty" + }, + { + "selector": "[data-freestar-ad]", + "type": "hide" + } + ] + }, { "domain": "psypost.com", "rules": [ @@ -3872,7 +3967,7 @@ ] }, "state": "enabled", - "hash": "0a8e98f8f0170311825b091ca29cfa7a" + "hash": "01a63fb9be9c1708398761f62f5f9598" }, "exceptionHandler": { "exceptions": [ @@ -4228,9 +4323,6 @@ { "domain": "tirerack.com" }, - { - "domain": "sephora.com" - }, { "domain": "earth.google.com" }, @@ -4256,7 +4348,7 @@ "privacy-test-pages.site" ] }, - "hash": "c34f2a525dac6f93a6d87bad377dbe9d" + "hash": "549a6e76edaf16c1fffced31b97e9553" }, "harmfulApis": { "settings": { @@ -4427,6 +4519,11 @@ "exceptions": [], "hash": "429cea8d27316dc62af04159ec7c42b5" }, + "mediaPlaybackRequiresUserGesture": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" + }, "navigatorInterface": { "exceptions": [ { @@ -6139,7 +6236,7 @@ "hubspot.com": { "rules": [ { - "rule": "js.hubspot.com/web-interactives-embed.js", + "rule": "hubspot.com/web-interactives", "domains": [ "" ] @@ -7571,7 +7668,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "374040a08f2e59051d7618509b9f65d4" + "hash": "174d90955e35a7140bcbe16d8764b52c" }, "trackingCookies1p": { "settings": { diff --git a/Core/trackerData.json b/Core/trackerData.json index 0d4648236e..2e842e28c9 100644 --- a/Core/trackerData.json +++ b/Core/trackerData.json @@ -1,6 +1,6 @@ { "_builtWith": { - "tracker-radar": "56bc133a7354c326d8afcb10b905e6cf865390022e9f2fc69045315332db9afd-4013b4e91930c643394cb31c6c745356f133b04f", + "tracker-radar": "9b6a3c1e62c8a97f63db77d2aef4f185129f05aad0f770425bcb54cbf8e0db84-4013b4e91930c643394cb31c6c745356f133b04f", "tracker-surrogates": "ba0d8cefe4432723ec75b998241efd2454dff35a" }, "readme": "https://github.com/duckduckgo/tracker-blocklists", @@ -418,7 +418,20 @@ "fingerprinting": 1, "cookies": 0, "categories": [], - "default": "block" + "default": "ignore", + "rules": [ + { + "rule": "a2z\\.com\\/resource\\/00000179-3cdd-d40f-a779-bedf7f820000\\/styleguide\\/All\\.min\\.7cb0b3550a4bbcbe1dbaee0522794cc9\\.gz\\.js", + "fingerprinting": 1, + "cookies": 0 + }, + { + "rule": "a2z\\.com\\/x\\.png", + "fingerprinting": 0, + "cookies": 0, + "comment": "pixel" + } + ] }, "aamsitecertifier.com": { "domain": "aamsitecertifier.com", @@ -451,7 +464,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -462,7 +475,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -508,7 +521,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2396,7 +2409,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2407,7 +2420,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2451,7 +2464,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2462,7 +2475,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2577,7 +2590,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2711,7 +2724,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2722,7 +2735,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3041,7 +3054,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3168,7 +3181,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3292,7 +3305,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3676,7 +3689,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3687,7 +3700,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3698,7 +3711,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3709,7 +3722,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3775,7 +3788,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3827,7 +3840,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3838,7 +3851,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3975,7 +3988,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4326,7 +4339,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4361,7 +4374,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4807,7 +4820,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4818,7 +4831,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5020,7 +5033,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5062,7 +5075,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5085,7 +5098,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5120,7 +5133,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5149,7 +5162,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5437,7 +5450,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5554,7 +5567,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5565,7 +5578,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5576,7 +5589,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5587,7 +5600,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5624,7 +5637,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5679,7 +5692,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6215,7 +6228,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6369,7 +6382,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6685,7 +6698,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6696,7 +6709,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6816,7 +6829,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7012,7 +7025,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7048,7 +7061,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7059,7 +7072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7070,7 +7083,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7653,7 +7666,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7664,7 +7677,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7675,7 +7688,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7756,7 +7769,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7879,7 +7892,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7890,7 +7903,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7968,7 +7981,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7979,7 +7992,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8094,7 +8107,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8245,7 +8258,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8256,7 +8269,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8747,7 +8760,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8758,7 +8771,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8799,7 +8812,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9215,7 +9228,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9820,7 +9833,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9831,7 +9844,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9842,7 +9855,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9882,7 +9895,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9924,7 +9937,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9978,7 +9991,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10046,7 +10059,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10057,7 +10070,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10105,7 +10118,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10231,7 +10244,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10391,7 +10404,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10402,7 +10415,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10413,7 +10426,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10440,7 +10453,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10490,7 +10503,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10537,7 +10550,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11015,7 +11028,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11055,7 +11068,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11791,7 +11804,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11880,7 +11893,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11891,7 +11904,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12113,7 +12126,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12153,7 +12166,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12164,7 +12177,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12175,7 +12188,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12186,7 +12199,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12512,7 +12525,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12523,7 +12536,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12534,7 +12547,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -14442,7 +14455,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15127,7 +15140,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15202,7 +15215,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15253,7 +15266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16112,7 +16125,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16123,7 +16136,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16152,7 +16165,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16371,7 +16384,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16680,7 +16693,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16691,7 +16704,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16876,7 +16889,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16925,7 +16938,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17199,7 +17212,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17210,7 +17223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17773,7 +17786,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18121,7 +18134,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18260,7 +18273,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18619,7 +18632,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18830,7 +18843,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20007,7 +20020,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20228,7 +20241,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20239,7 +20252,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20250,7 +20263,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20390,7 +20403,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20877,7 +20890,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20906,7 +20919,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20917,7 +20930,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20928,7 +20941,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20992,7 +21005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21072,7 +21085,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21120,7 +21133,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21131,7 +21144,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21171,7 +21184,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21182,7 +21195,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21217,7 +21230,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21228,7 +21241,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21361,7 +21374,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21445,7 +21458,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21916,7 +21929,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21927,7 +21940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21938,7 +21951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22008,7 +22021,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22019,7 +22032,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22172,7 +22185,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22183,7 +22196,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22225,7 +22238,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22236,7 +22249,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22247,7 +22260,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22517,7 +22530,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22528,7 +22541,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22589,7 +22602,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22647,7 +22660,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22658,7 +22671,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22758,7 +22771,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22781,7 +22794,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22792,7 +22805,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23149,7 +23162,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23253,7 +23266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23389,7 +23402,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23464,7 +23477,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23475,7 +23488,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23519,7 +23532,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23530,7 +23543,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23541,7 +23554,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23552,7 +23565,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23632,7 +23645,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23665,7 +23678,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23716,7 +23729,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23852,7 +23865,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23981,7 +23994,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23992,7 +24005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24239,7 +24252,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24250,7 +24263,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24261,7 +24274,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24542,7 +24555,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24577,7 +24590,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24618,7 +24631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24734,7 +24747,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24745,7 +24758,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24774,7 +24787,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24870,7 +24883,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24927,7 +24940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24938,7 +24951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25059,7 +25072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25216,7 +25229,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25242,7 +25255,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25253,7 +25266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25316,7 +25329,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25327,7 +25340,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25338,7 +25351,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25569,7 +25582,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25596,7 +25609,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25607,7 +25620,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25618,7 +25631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25648,7 +25661,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25659,7 +25672,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25687,7 +25700,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25698,7 +25711,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25771,7 +25784,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25819,7 +25832,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25830,7 +25843,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25841,7 +25854,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25852,7 +25865,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25863,7 +25876,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25874,7 +25887,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25946,7 +25959,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25957,7 +25970,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26011,7 +26024,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26213,7 +26226,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26521,7 +26534,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26532,7 +26545,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26543,7 +26556,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26794,7 +26807,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26805,7 +26818,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -27223,7 +27236,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28080,7 +28093,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28210,7 +28223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28221,7 +28234,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28354,7 +28367,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28437,7 +28450,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28448,7 +28461,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28809,7 +28822,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29116,7 +29129,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29127,7 +29140,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29253,7 +29266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29264,7 +29277,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -31006,7 +31019,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -31311,7 +31324,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32471,7 +32484,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32482,7 +32495,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32493,7 +32506,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32504,7 +32517,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32515,7 +32528,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32526,7 +32539,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32537,7 +32550,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32548,7 +32561,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32559,7 +32572,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32570,7 +32583,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32581,7 +32594,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32592,7 +32605,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32603,7 +32616,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32614,7 +32627,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32625,7 +32638,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32636,7 +32649,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32647,7 +32660,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32658,7 +32671,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32669,7 +32682,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32680,7 +32693,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "ambientdusk.com": { + "domain": "ambientdusk.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32691,7 +32715,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "ambrosialsummit.com": { + "domain": "ambrosialsummit.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32702,7 +32737,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32713,7 +32748,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32724,7 +32759,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "analyzecorona.com": { + "domain": "analyzecorona.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32735,7 +32781,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32746,7 +32792,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32757,7 +32803,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32768,7 +32814,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32779,7 +32825,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32790,7 +32836,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32801,7 +32847,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32812,7 +32858,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32823,7 +32869,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32834,7 +32880,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32845,7 +32891,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32856,7 +32902,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32867,7 +32913,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32878,7 +32924,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32889,7 +32935,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32900,7 +32946,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32911,7 +32957,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32922,7 +32968,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32933,7 +32979,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32944,7 +32990,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32955,7 +33001,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32966,7 +33012,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32977,7 +33023,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32988,7 +33034,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32999,7 +33045,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33010,7 +33056,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33021,7 +33067,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33032,7 +33078,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33043,7 +33089,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33054,7 +33100,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33065,7 +33111,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33076,7 +33122,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33087,7 +33133,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33098,7 +33144,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33109,7 +33155,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33120,7 +33166,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33131,7 +33177,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33142,7 +33188,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33153,7 +33199,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33164,7 +33210,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33175,7 +33221,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33186,7 +33232,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33197,7 +33243,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33208,7 +33254,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33219,7 +33265,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33230,7 +33276,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33241,7 +33287,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33252,7 +33298,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33263,7 +33309,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33274,7 +33320,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33285,7 +33331,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33296,7 +33342,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33307,7 +33353,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33318,7 +33364,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33329,7 +33375,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33340,7 +33386,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33351,7 +33397,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33362,7 +33408,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33373,7 +33419,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "celestialquasar.com": { + "domain": "celestialquasar.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33384,7 +33441,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33395,7 +33452,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33406,7 +33463,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33417,7 +33474,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33428,7 +33485,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33439,7 +33496,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33450,7 +33507,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33461,7 +33518,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33472,7 +33529,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33483,7 +33540,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33494,7 +33551,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33505,7 +33562,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33516,7 +33573,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33527,7 +33584,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33538,7 +33595,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33549,7 +33606,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33560,7 +33617,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33571,7 +33628,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33582,7 +33639,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33593,7 +33650,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33604,7 +33661,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33615,7 +33672,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33626,7 +33683,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33637,7 +33694,40 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "coordinatedcoat.com": { + "domain": "coordinatedcoat.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "copycarpenter.com": { + "domain": "copycarpenter.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "cosmicsculptor.com": { + "domain": "cosmicsculptor.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33648,7 +33738,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33659,7 +33749,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33670,7 +33760,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33681,7 +33771,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33692,7 +33782,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33703,7 +33793,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33714,7 +33804,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33725,7 +33815,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33736,7 +33826,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33747,7 +33837,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33758,7 +33848,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33769,7 +33859,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33780,7 +33870,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33791,7 +33881,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33802,7 +33892,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33813,7 +33903,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33824,7 +33914,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33835,7 +33925,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33846,7 +33936,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33857,7 +33947,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33868,7 +33958,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33879,7 +33969,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33890,7 +33980,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33901,7 +33991,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33912,7 +34002,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "deliciousducks.com": { + "domain": "deliciousducks.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "dependenttrip.com": { + "domain": "dependenttrip.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33923,7 +34035,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33934,7 +34046,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33945,7 +34057,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33956,7 +34068,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33967,7 +34079,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33978,7 +34090,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33989,7 +34101,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34000,7 +34112,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34011,7 +34123,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34022,7 +34134,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34033,7 +34145,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "effervescentvista.com": { + "domain": "effervescentvista.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34044,7 +34167,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34055,7 +34178,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34066,7 +34189,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "enchantingmystique.com": { + "domain": "enchantingmystique.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34077,7 +34211,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34088,7 +34222,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "enigmaticcanyon.com": { + "domain": "enigmaticcanyon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "enigmaticvoyage.com": { + "domain": "enigmaticvoyage.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34099,7 +34255,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34110,7 +34266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34121,7 +34277,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34132,7 +34288,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "evasivejar.com": { + "domain": "evasivejar.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34143,7 +34310,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34154,7 +34321,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34165,7 +34332,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34176,7 +34343,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34187,7 +34354,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34198,7 +34365,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34209,7 +34376,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34220,7 +34387,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34231,7 +34398,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34242,7 +34409,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34253,7 +34420,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34264,7 +34431,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34275,7 +34442,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34286,7 +34453,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34297,7 +34464,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34308,7 +34475,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34319,7 +34486,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34330,7 +34497,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34341,7 +34508,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34352,7 +34519,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34363,7 +34530,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34374,7 +34541,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34385,7 +34552,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34396,7 +34563,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34407,7 +34574,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34418,7 +34585,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34429,7 +34596,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34440,7 +34607,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34451,7 +34618,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34462,7 +34629,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "friendlycrayon.com": { + "domain": "friendlycrayon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34473,7 +34651,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34484,7 +34662,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34495,7 +34673,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34506,7 +34684,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34517,7 +34695,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34528,7 +34706,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34539,7 +34717,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34550,7 +34728,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34561,7 +34739,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34572,7 +34750,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34583,7 +34761,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34594,7 +34772,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34605,7 +34783,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34616,7 +34794,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "gracefulmilk.com": { + "domain": "gracefulmilk.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34627,7 +34816,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34638,7 +34827,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34649,7 +34838,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34660,7 +34849,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34671,7 +34860,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34682,7 +34871,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34693,7 +34882,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34704,7 +34893,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34715,7 +34904,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "halcyoncanyon.com": { + "domain": "halcyoncanyon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "halcyonsculpture.com": { + "domain": "halcyonsculpture.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34726,7 +34937,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34737,7 +34948,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34748,7 +34959,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34759,7 +34970,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34770,7 +34981,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34781,7 +34992,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34792,7 +35003,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34803,7 +35014,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34814,7 +35025,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34825,7 +35036,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34836,7 +35047,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34847,7 +35058,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34858,7 +35069,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34869,7 +35080,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34880,7 +35091,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34891,7 +35102,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34902,7 +35113,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34913,7 +35124,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34924,7 +35135,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34935,7 +35146,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34946,7 +35157,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34957,7 +35168,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34968,7 +35179,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34979,7 +35190,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34990,7 +35201,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35001,7 +35212,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35012,7 +35223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35023,7 +35234,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35034,7 +35245,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35045,7 +35256,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35056,7 +35267,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35067,7 +35278,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35078,7 +35289,40 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "jubilantcascade.com": { + "domain": "jubilantcascade.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "jubilantglimmer.com": { + "domain": "jubilantglimmer.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "jubilantwhisper.com": { + "domain": "jubilantwhisper.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35089,7 +35333,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35100,7 +35344,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35111,7 +35355,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35122,7 +35366,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35133,7 +35377,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35144,7 +35388,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35155,7 +35399,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35166,7 +35410,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35177,7 +35421,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35188,7 +35432,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35199,7 +35443,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35210,7 +35454,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "loadsurprise.com": { + "domain": "loadsurprise.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35221,7 +35476,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35232,7 +35487,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35243,7 +35498,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35254,7 +35509,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35265,7 +35520,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "luminouscatalyst.com": { + "domain": "luminouscatalyst.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35276,7 +35542,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "lustroushaven.com": { + "domain": "lustroushaven.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35287,7 +35564,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35298,7 +35575,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35309,7 +35586,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35320,7 +35597,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35331,7 +35608,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35342,7 +35619,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35353,7 +35630,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35364,7 +35641,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35375,7 +35652,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35386,7 +35663,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35397,7 +35674,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35408,7 +35685,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35419,7 +35696,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35430,7 +35707,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35441,7 +35718,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35452,7 +35729,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35463,7 +35740,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35474,7 +35751,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35485,7 +35762,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35496,7 +35773,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35507,7 +35784,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "mysticalagoon.com": { + "domain": "mysticalagoon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35518,7 +35806,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35529,7 +35817,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "nebulacrescent.com": { + "domain": "nebulacrescent.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "nebulajubilee.com": { + "domain": "nebulajubilee.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35540,7 +35850,40 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "nebulousgarden.com": { + "domain": "nebulousgarden.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "nebulousquasar.com": { + "domain": "nebulousquasar.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "nebulousripple.com": { + "domain": "nebulousripple.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35551,7 +35894,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "niftyhospital.com": { + "domain": "niftyhospital.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35562,7 +35916,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35573,7 +35927,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35584,7 +35938,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35595,7 +35949,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35606,7 +35960,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35617,7 +35971,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35628,7 +35982,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35639,7 +35993,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35650,7 +36004,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35661,7 +36015,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35672,7 +36026,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35683,7 +36037,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35694,7 +36048,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35705,7 +36059,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35716,7 +36070,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35727,7 +36081,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "parallelbulb.com": { + "domain": "parallelbulb.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35738,7 +36103,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35749,7 +36114,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35760,7 +36125,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35771,7 +36136,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "piquantvortex.com": { + "domain": "piquantvortex.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35782,7 +36158,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35793,7 +36169,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35804,7 +36180,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35815,7 +36191,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35826,7 +36202,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35837,7 +36213,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "pointlessprofit.com": { + "domain": "pointlessprofit.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35848,7 +36235,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35859,7 +36246,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35870,7 +36257,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35881,7 +36268,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35892,7 +36279,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35903,7 +36290,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35914,7 +36301,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35925,7 +36312,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35936,7 +36323,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35947,7 +36334,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35958,7 +36345,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35969,7 +36356,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35980,7 +36367,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35991,7 +36378,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36002,7 +36389,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "radiantlullaby.com": { + "domain": "radiantlullaby.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36013,7 +36411,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36024,7 +36422,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36035,7 +36433,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36046,7 +36444,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36057,7 +36455,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36068,7 +36466,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36079,7 +36477,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36090,7 +36488,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36101,7 +36499,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36112,7 +36510,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36123,7 +36521,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "reconditeprison.com": { + "domain": "reconditeprison.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36134,7 +36543,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36145,7 +36554,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36156,7 +36565,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36167,7 +36576,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36178,7 +36587,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36189,7 +36598,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "resplendentecho.com": { + "domain": "resplendentecho.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36200,7 +36620,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36211,7 +36631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36222,7 +36642,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36233,7 +36653,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36244,7 +36664,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36255,7 +36675,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36266,7 +36686,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36277,7 +36697,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36288,7 +36708,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36299,7 +36719,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36310,7 +36730,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36321,7 +36741,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36332,7 +36752,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "scaredslip.com": { + "domain": "scaredslip.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36343,7 +36774,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36354,7 +36785,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36365,7 +36796,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36376,7 +36807,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36387,7 +36818,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36398,7 +36829,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36409,7 +36840,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36420,7 +36851,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36431,7 +36862,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36442,7 +36873,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36453,7 +36884,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36464,7 +36895,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36475,7 +36906,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36486,7 +36917,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36497,7 +36928,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "seraphicjubilee.com": { + "domain": "seraphicjubilee.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "serenepebble.com": { + "domain": "serenepebble.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36508,7 +36961,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36519,7 +36972,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36530,7 +36983,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36541,7 +36994,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36552,7 +37005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36563,7 +37016,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36574,7 +37027,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36585,7 +37038,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36596,7 +37049,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36607,7 +37060,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36618,7 +37071,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36629,7 +37082,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36640,7 +37093,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36651,7 +37104,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36662,7 +37115,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36673,7 +37126,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36684,7 +37137,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36695,7 +37148,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36706,7 +37159,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36717,7 +37170,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36728,7 +37181,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36739,7 +37192,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36750,7 +37203,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36761,7 +37214,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36772,7 +37225,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36783,7 +37236,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "soggyzoo.com": { + "domain": "soggyzoo.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36794,7 +37258,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36805,7 +37269,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36816,7 +37280,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36827,7 +37291,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36838,7 +37302,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "soresidewalk.com": { + "domain": "soresidewalk.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36849,7 +37324,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36860,7 +37335,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36871,7 +37346,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36882,7 +37357,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36893,7 +37368,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36904,7 +37379,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36915,7 +37390,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36926,7 +37401,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36937,7 +37412,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36948,7 +37423,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36959,7 +37434,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36970,7 +37445,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36981,7 +37456,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36992,7 +37467,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37003,7 +37478,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37014,7 +37489,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37025,7 +37500,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37036,7 +37511,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37047,7 +37522,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37058,7 +37533,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37069,7 +37544,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37080,7 +37555,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37091,7 +37566,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37102,7 +37577,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37113,7 +37588,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37124,7 +37599,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37135,7 +37610,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37146,7 +37621,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37157,7 +37632,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37168,7 +37643,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37179,7 +37654,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37190,7 +37665,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "stripedbat.com": { + "domain": "stripedbat.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37201,7 +37687,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37212,7 +37698,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37223,7 +37709,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37234,7 +37720,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37245,7 +37731,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37256,7 +37742,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37267,7 +37753,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37278,7 +37764,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37289,7 +37775,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37300,7 +37786,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37311,7 +37797,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37322,7 +37808,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37333,7 +37819,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37344,7 +37830,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37355,7 +37841,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37366,7 +37852,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37377,7 +37863,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37388,7 +37874,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37399,7 +37885,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37410,7 +37896,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37421,7 +37907,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37432,7 +37918,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37443,7 +37929,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "thingstaste.com": { + "domain": "thingstaste.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37454,7 +37951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37465,7 +37962,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37476,7 +37973,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37487,7 +37984,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37498,7 +37995,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37509,7 +38006,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37520,7 +38017,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "tranquilcan.com": { + "domain": "tranquilcan.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37531,7 +38039,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "tranquilplume.com": { + "domain": "tranquilplume.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37542,7 +38061,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37553,7 +38072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37564,7 +38083,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37575,7 +38094,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37586,7 +38105,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37597,7 +38116,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37608,7 +38127,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37619,7 +38138,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37630,7 +38149,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37641,7 +38160,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37652,7 +38171,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37663,7 +38182,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37674,7 +38193,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37685,7 +38204,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37696,7 +38215,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37707,7 +38226,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37718,7 +38237,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37729,7 +38248,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37740,7 +38259,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37751,7 +38270,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37762,7 +38281,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37773,7 +38292,40 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "vanishmemory.com": { + "domain": "vanishmemory.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "velvetquasar.com": { + "domain": "velvetquasar.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "venomousvessel.com": { + "domain": "venomousvessel.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37784,7 +38336,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37795,7 +38347,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "verdantloom.com": { + "domain": "verdantloom.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37806,7 +38369,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "vibrantgale.com": { + "domain": "vibrantgale.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37817,7 +38391,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "vibranttalisman.com": { + "domain": "vibranttalisman.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37828,7 +38413,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "vividmeadow.com": { + "domain": "vividmeadow.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37839,7 +38435,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37850,7 +38446,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37861,7 +38457,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37872,7 +38468,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "whimsicalcanyon.com": { + "domain": "whimsicalcanyon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37883,7 +38490,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37894,7 +38501,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "whisperingquasar.com": { + "domain": "whisperingquasar.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37905,7 +38523,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37916,7 +38534,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37927,7 +38545,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "wistfulwaste.com": { + "domain": "wistfulwaste.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37938,7 +38567,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "wretchedfloor.com": { + "domain": "wretchedfloor.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37949,7 +38589,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "zephyrlabyrinth.com": { + "domain": "zephyrlabyrinth.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37960,7 +38611,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37971,7 +38622,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0146, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -47997,11 +48648,14 @@ "aliveachiever.com", "alluringbucket.com", "aloofvest.com", + "ambientdusk.com", "ambiguousafternoon.com", "ambiguousdinosaurs.com", + "ambrosialsummit.com", "amethystzenith.com", "amuckafternoon.com", "amusedbucket.com", + "analyzecorona.com", "ancientact.com", "annoyedairport.com", "annoyingacoustics.com", @@ -48083,6 +48737,7 @@ "cautiouscredit.com", "cavecurtain.com", "ceciliavenus.com", + "celestialquasar.com", "celestialspectra.com", "chalkoil.com", "changeablecats.com", @@ -48116,6 +48771,9 @@ "confusedcart.com", "consciouscheese.com", "consciousdirt.com", + "coordinatedcoat.com", + "copycarpenter.com", + "cosmicsculptor.com", "courageousbaby.com", "coverapparatus.com", "cozyhillside.com", @@ -48152,6 +48810,8 @@ "deerbeginner.com", "defeatedbadge.com", "delicatecascade.com", + "deliciousducks.com", + "dependenttrip.com", "detailedkitten.com", "detectdiscovery.com", "devilishdinner.com", @@ -48170,18 +48830,23 @@ "dreamycanyon.com", "dustydime.com", "dustyhammer.com", + "effervescentvista.com", "elasticchange.com", "elderlybean.com", "eminentbubble.com", + "enchantingmystique.com", "encouragingthread.com", "endurablebulb.com", "energeticladybug.com", + "enigmaticcanyon.com", + "enigmaticvoyage.com", "enormousearth.com", "entertainskin.com", "enviousshape.com", "equablekettle.com", "ethereallagoon.com", "evanescentedge.com", + "evasivejar.com", "eventexistence.com", "exampleshake.com", "excitingtub.com", @@ -48225,6 +48890,7 @@ "franticroof.com", "freezingbuilding.com", "frequentflesh.com", + "friendlycrayon.com", "friendwool.com", "fronttoad.com", "fumblingform.com", @@ -48246,6 +48912,7 @@ "gloriousbeef.com", "gondolagnome.com", "gorgeousedge.com", + "gracefulmilk.com", "grainmass.com", "grandfatherguitar.com", "grayoranges.com", @@ -48258,6 +48925,8 @@ "guiltlessbasketball.com", "gulliblegrip.com", "gustygrandmother.com", + "halcyoncanyon.com", + "halcyonsculpture.com", "hallowedinvention.com", "haltingbadge.com", "haltingdivision.com", @@ -48299,6 +48968,9 @@ "internalsink.com", "j93557g.com", "jubilantcanyon.com", + "jubilantcascade.com", + "jubilantglimmer.com", + "jubilantwhisper.com", "kaputquill.com", "knitstamp.com", "knottyswing.com", @@ -48314,6 +48986,7 @@ "livelyreward.com", "livingsleet.com", "lizardslaugh.com", + "loadsurprise.com", "lonelyflavor.com", "longingtrees.com", "looseloaf.com", @@ -48321,8 +48994,10 @@ "losslace.com", "lovelydrum.com", "ludicrousarch.com", + "luminouscatalyst.com", "lumpylumber.com", "lunchroomlock.com", + "lustroushaven.com", "maddeningpowder.com", "maliciousmusic.com", "marketspiders.com", @@ -48352,12 +49027,19 @@ "mundanenail.com", "mushywaste.com", "muteknife.com", + "mysticalagoon.com", "naivestatement.com", "nappyattack.com", "neatshade.com", + "nebulacrescent.com", + "nebulajubilee.com", "nebulousamusement.com", + "nebulousgarden.com", + "nebulousquasar.com", + "nebulousripple.com", "needlessnorth.com", "nervoussummer.com", + "niftyhospital.com", "nightwound.com", "nondescriptcrowd.com", "nondescriptnote.com", @@ -48380,12 +49062,14 @@ "panickycurtain.com", "panickypancake.com", "panoramicplane.com", + "parallelbulb.com", "parchedsofa.com", "parentpicture.com", "partplanes.com", "passivepolo.com", "peacefullimit.com", "petiteumbrella.com", + "piquantvortex.com", "placidactivity.com", "placidperson.com", "planebasin.com", @@ -48397,6 +49081,7 @@ "poeticpackage.com", "pointdigestion.com", "pointlesspocket.com", + "pointlessprofit.com", "politeplanes.com", "politicalporter.com", "possibleboats.com", @@ -48424,6 +49109,7 @@ "quizzicalzephyr.com", "rabbitbreath.com", "rabbitrifle.com", + "radiantlullaby.com", "radiateprose.com", "railwaygiraffe.com", "railwayreason.com", @@ -48440,6 +49126,7 @@ "rebelswing.com", "receptivereaction.com", "recessrain.com", + "reconditeprison.com", "reconditerake.com", "reconditerespect.com", "reflectivestatement.com", @@ -48451,6 +49138,7 @@ "resonantbrush.com", "resonantrock.com", "respectrain.com", + "resplendentecho.com", "restrainstorm.com", "restructureinvention.com", "retrievemint.com", @@ -48474,6 +49162,7 @@ "savoryorange.com", "scarceshock.com", "scaredcomfort.com", + "scaredslip.com", "scaredsnake.com", "scaredsnakes.com", "scaredsong.com", @@ -48499,6 +49188,8 @@ "selectivesummer.com", "selfishsnake.com", "separatesort.com", + "seraphicjubilee.com", + "serenepebble.com", "serioussuit.com", "serpentshampoo.com", "settleshoes.com", @@ -48538,12 +49229,14 @@ "smoggysongs.com", "sneakwind.com", "soggysponge.com", + "soggyzoo.com", "solarislabyrinth.com", "somberscarecrow.com", "sombersticks.com", "songsterritory.com", "soothingglade.com", "sordidsmile.com", + "soresidewalk.com", "soretrain.com", "sortsail.com", "sortsummer.com", @@ -48590,6 +49283,7 @@ "stretchsister.com", "stretchsneeze.com", "stretchsquirrel.com", + "stripedbat.com", "strivesidewalk.com", "strivesquirrel.com", "strokesystem.com", @@ -48626,6 +49320,7 @@ "tendertest.com", "terriblethumb.com", "terrifictooth.com", + "thingstaste.com", "thinkitten.com", "thirdrespect.com", "thomastorch.com", @@ -48636,7 +49331,9 @@ "tiredthroat.com", "tiresomethunder.com", "tradetooth.com", + "tranquilcan.com", "tranquilcanyon.com", + "tranquilplume.com", "tremendousearthquake.com", "tremendousplastic.com", "tritebadge.com", @@ -48665,12 +49362,19 @@ "unwieldyimpulse.com", "unwieldyplastic.com", "uselesslumber.com", + "vanishmemory.com", + "velvetquasar.com", "vengefulgrass.com", + "venomousvessel.com", "venusgloria.com", "verdantanswer.com", + "verdantloom.com", "verseballs.com", + "vibrantgale.com", "vibranthaven.com", + "vibranttalisman.com", "virtualvincent.com", + "vividmeadow.com", "volatileprofit.com", "volatilevessel.com", "voraciousgrip.com", @@ -48679,18 +49383,23 @@ "warmquiver.com", "wearbasin.com", "wellgroomedhydrant.com", + "whimsicalcanyon.com", "whimsicalgrove.com", "whisperingcascade.com", + "whisperingquasar.com", "whisperingsummit.com", "whispermeeting.com", "wildcommittee.com", + "wistfulwaste.com", "workoperation.com", + "wretchedfloor.com", "wrongwound.com", + "zephyrlabyrinth.com", "zestycrime.com", "zipperxray.com", "zlp6s.pw" ], - "prevalence": 0.0151, + "prevalence": 0.0146, "displayName": "Admiral" } }, @@ -49436,11 +50145,14 @@ "aliveachiever.com": "Leven Labs, Inc. DBA Admiral", "alluringbucket.com": "Leven Labs, Inc. DBA Admiral", "aloofvest.com": "Leven Labs, Inc. DBA Admiral", + "ambientdusk.com": "Leven Labs, Inc. DBA Admiral", "ambiguousafternoon.com": "Leven Labs, Inc. DBA Admiral", "ambiguousdinosaurs.com": "Leven Labs, Inc. DBA Admiral", + "ambrosialsummit.com": "Leven Labs, Inc. DBA Admiral", "amethystzenith.com": "Leven Labs, Inc. DBA Admiral", "amuckafternoon.com": "Leven Labs, Inc. DBA Admiral", "amusedbucket.com": "Leven Labs, Inc. DBA Admiral", + "analyzecorona.com": "Leven Labs, Inc. DBA Admiral", "ancientact.com": "Leven Labs, Inc. DBA Admiral", "annoyedairport.com": "Leven Labs, Inc. DBA Admiral", "annoyingacoustics.com": "Leven Labs, Inc. DBA Admiral", @@ -49522,6 +50234,7 @@ "cautiouscredit.com": "Leven Labs, Inc. DBA Admiral", "cavecurtain.com": "Leven Labs, Inc. DBA Admiral", "ceciliavenus.com": "Leven Labs, Inc. DBA Admiral", + "celestialquasar.com": "Leven Labs, Inc. DBA Admiral", "celestialspectra.com": "Leven Labs, Inc. DBA Admiral", "chalkoil.com": "Leven Labs, Inc. DBA Admiral", "changeablecats.com": "Leven Labs, Inc. DBA Admiral", @@ -49555,6 +50268,9 @@ "confusedcart.com": "Leven Labs, Inc. DBA Admiral", "consciouscheese.com": "Leven Labs, Inc. DBA Admiral", "consciousdirt.com": "Leven Labs, Inc. DBA Admiral", + "coordinatedcoat.com": "Leven Labs, Inc. DBA Admiral", + "copycarpenter.com": "Leven Labs, Inc. DBA Admiral", + "cosmicsculptor.com": "Leven Labs, Inc. DBA Admiral", "courageousbaby.com": "Leven Labs, Inc. DBA Admiral", "coverapparatus.com": "Leven Labs, Inc. DBA Admiral", "cozyhillside.com": "Leven Labs, Inc. DBA Admiral", @@ -49591,6 +50307,8 @@ "deerbeginner.com": "Leven Labs, Inc. DBA Admiral", "defeatedbadge.com": "Leven Labs, Inc. DBA Admiral", "delicatecascade.com": "Leven Labs, Inc. DBA Admiral", + "deliciousducks.com": "Leven Labs, Inc. DBA Admiral", + "dependenttrip.com": "Leven Labs, Inc. DBA Admiral", "detailedkitten.com": "Leven Labs, Inc. DBA Admiral", "detectdiscovery.com": "Leven Labs, Inc. DBA Admiral", "devilishdinner.com": "Leven Labs, Inc. DBA Admiral", @@ -49609,18 +50327,23 @@ "dreamycanyon.com": "Leven Labs, Inc. DBA Admiral", "dustydime.com": "Leven Labs, Inc. DBA Admiral", "dustyhammer.com": "Leven Labs, Inc. DBA Admiral", + "effervescentvista.com": "Leven Labs, Inc. DBA Admiral", "elasticchange.com": "Leven Labs, Inc. DBA Admiral", "elderlybean.com": "Leven Labs, Inc. DBA Admiral", "eminentbubble.com": "Leven Labs, Inc. DBA Admiral", + "enchantingmystique.com": "Leven Labs, Inc. DBA Admiral", "encouragingthread.com": "Leven Labs, Inc. DBA Admiral", "endurablebulb.com": "Leven Labs, Inc. DBA Admiral", "energeticladybug.com": "Leven Labs, Inc. DBA Admiral", + "enigmaticcanyon.com": "Leven Labs, Inc. DBA Admiral", + "enigmaticvoyage.com": "Leven Labs, Inc. DBA Admiral", "enormousearth.com": "Leven Labs, Inc. DBA Admiral", "entertainskin.com": "Leven Labs, Inc. DBA Admiral", "enviousshape.com": "Leven Labs, Inc. DBA Admiral", "equablekettle.com": "Leven Labs, Inc. DBA Admiral", "ethereallagoon.com": "Leven Labs, Inc. DBA Admiral", "evanescentedge.com": "Leven Labs, Inc. DBA Admiral", + "evasivejar.com": "Leven Labs, Inc. DBA Admiral", "eventexistence.com": "Leven Labs, Inc. DBA Admiral", "exampleshake.com": "Leven Labs, Inc. DBA Admiral", "excitingtub.com": "Leven Labs, Inc. DBA Admiral", @@ -49664,6 +50387,7 @@ "franticroof.com": "Leven Labs, Inc. DBA Admiral", "freezingbuilding.com": "Leven Labs, Inc. DBA Admiral", "frequentflesh.com": "Leven Labs, Inc. DBA Admiral", + "friendlycrayon.com": "Leven Labs, Inc. DBA Admiral", "friendwool.com": "Leven Labs, Inc. DBA Admiral", "fronttoad.com": "Leven Labs, Inc. DBA Admiral", "fumblingform.com": "Leven Labs, Inc. DBA Admiral", @@ -49685,6 +50409,7 @@ "gloriousbeef.com": "Leven Labs, Inc. DBA Admiral", "gondolagnome.com": "Leven Labs, Inc. DBA Admiral", "gorgeousedge.com": "Leven Labs, Inc. DBA Admiral", + "gracefulmilk.com": "Leven Labs, Inc. DBA Admiral", "grainmass.com": "Leven Labs, Inc. DBA Admiral", "grandfatherguitar.com": "Leven Labs, Inc. DBA Admiral", "grayoranges.com": "Leven Labs, Inc. DBA Admiral", @@ -49697,6 +50422,8 @@ "guiltlessbasketball.com": "Leven Labs, Inc. DBA Admiral", "gulliblegrip.com": "Leven Labs, Inc. DBA Admiral", "gustygrandmother.com": "Leven Labs, Inc. DBA Admiral", + "halcyoncanyon.com": "Leven Labs, Inc. DBA Admiral", + "halcyonsculpture.com": "Leven Labs, Inc. DBA Admiral", "hallowedinvention.com": "Leven Labs, Inc. DBA Admiral", "haltingbadge.com": "Leven Labs, Inc. DBA Admiral", "haltingdivision.com": "Leven Labs, Inc. DBA Admiral", @@ -49738,6 +50465,9 @@ "internalsink.com": "Leven Labs, Inc. DBA Admiral", "j93557g.com": "Leven Labs, Inc. DBA Admiral", "jubilantcanyon.com": "Leven Labs, Inc. DBA Admiral", + "jubilantcascade.com": "Leven Labs, Inc. DBA Admiral", + "jubilantglimmer.com": "Leven Labs, Inc. DBA Admiral", + "jubilantwhisper.com": "Leven Labs, Inc. DBA Admiral", "kaputquill.com": "Leven Labs, Inc. DBA Admiral", "knitstamp.com": "Leven Labs, Inc. DBA Admiral", "knottyswing.com": "Leven Labs, Inc. DBA Admiral", @@ -49753,6 +50483,7 @@ "livelyreward.com": "Leven Labs, Inc. DBA Admiral", "livingsleet.com": "Leven Labs, Inc. DBA Admiral", "lizardslaugh.com": "Leven Labs, Inc. DBA Admiral", + "loadsurprise.com": "Leven Labs, Inc. DBA Admiral", "lonelyflavor.com": "Leven Labs, Inc. DBA Admiral", "longingtrees.com": "Leven Labs, Inc. DBA Admiral", "looseloaf.com": "Leven Labs, Inc. DBA Admiral", @@ -49760,8 +50491,10 @@ "losslace.com": "Leven Labs, Inc. DBA Admiral", "lovelydrum.com": "Leven Labs, Inc. DBA Admiral", "ludicrousarch.com": "Leven Labs, Inc. DBA Admiral", + "luminouscatalyst.com": "Leven Labs, Inc. DBA Admiral", "lumpylumber.com": "Leven Labs, Inc. DBA Admiral", "lunchroomlock.com": "Leven Labs, Inc. DBA Admiral", + "lustroushaven.com": "Leven Labs, Inc. DBA Admiral", "maddeningpowder.com": "Leven Labs, Inc. DBA Admiral", "maliciousmusic.com": "Leven Labs, Inc. DBA Admiral", "marketspiders.com": "Leven Labs, Inc. DBA Admiral", @@ -49791,12 +50524,19 @@ "mundanenail.com": "Leven Labs, Inc. DBA Admiral", "mushywaste.com": "Leven Labs, Inc. DBA Admiral", "muteknife.com": "Leven Labs, Inc. DBA Admiral", + "mysticalagoon.com": "Leven Labs, Inc. DBA Admiral", "naivestatement.com": "Leven Labs, Inc. DBA Admiral", "nappyattack.com": "Leven Labs, Inc. DBA Admiral", "neatshade.com": "Leven Labs, Inc. DBA Admiral", + "nebulacrescent.com": "Leven Labs, Inc. DBA Admiral", + "nebulajubilee.com": "Leven Labs, Inc. DBA Admiral", "nebulousamusement.com": "Leven Labs, Inc. DBA Admiral", + "nebulousgarden.com": "Leven Labs, Inc. DBA Admiral", + "nebulousquasar.com": "Leven Labs, Inc. DBA Admiral", + "nebulousripple.com": "Leven Labs, Inc. DBA Admiral", "needlessnorth.com": "Leven Labs, Inc. DBA Admiral", "nervoussummer.com": "Leven Labs, Inc. DBA Admiral", + "niftyhospital.com": "Leven Labs, Inc. DBA Admiral", "nightwound.com": "Leven Labs, Inc. DBA Admiral", "nondescriptcrowd.com": "Leven Labs, Inc. DBA Admiral", "nondescriptnote.com": "Leven Labs, Inc. DBA Admiral", @@ -49819,12 +50559,14 @@ "panickycurtain.com": "Leven Labs, Inc. DBA Admiral", "panickypancake.com": "Leven Labs, Inc. DBA Admiral", "panoramicplane.com": "Leven Labs, Inc. DBA Admiral", + "parallelbulb.com": "Leven Labs, Inc. DBA Admiral", "parchedsofa.com": "Leven Labs, Inc. DBA Admiral", "parentpicture.com": "Leven Labs, Inc. DBA Admiral", "partplanes.com": "Leven Labs, Inc. DBA Admiral", "passivepolo.com": "Leven Labs, Inc. DBA Admiral", "peacefullimit.com": "Leven Labs, Inc. DBA Admiral", "petiteumbrella.com": "Leven Labs, Inc. DBA Admiral", + "piquantvortex.com": "Leven Labs, Inc. DBA Admiral", "placidactivity.com": "Leven Labs, Inc. DBA Admiral", "placidperson.com": "Leven Labs, Inc. DBA Admiral", "planebasin.com": "Leven Labs, Inc. DBA Admiral", @@ -49836,6 +50578,7 @@ "poeticpackage.com": "Leven Labs, Inc. DBA Admiral", "pointdigestion.com": "Leven Labs, Inc. DBA Admiral", "pointlesspocket.com": "Leven Labs, Inc. DBA Admiral", + "pointlessprofit.com": "Leven Labs, Inc. DBA Admiral", "politeplanes.com": "Leven Labs, Inc. DBA Admiral", "politicalporter.com": "Leven Labs, Inc. DBA Admiral", "possibleboats.com": "Leven Labs, Inc. DBA Admiral", @@ -49863,6 +50606,7 @@ "quizzicalzephyr.com": "Leven Labs, Inc. DBA Admiral", "rabbitbreath.com": "Leven Labs, Inc. DBA Admiral", "rabbitrifle.com": "Leven Labs, Inc. DBA Admiral", + "radiantlullaby.com": "Leven Labs, Inc. DBA Admiral", "radiateprose.com": "Leven Labs, Inc. DBA Admiral", "railwaygiraffe.com": "Leven Labs, Inc. DBA Admiral", "railwayreason.com": "Leven Labs, Inc. DBA Admiral", @@ -49879,6 +50623,7 @@ "rebelswing.com": "Leven Labs, Inc. DBA Admiral", "receptivereaction.com": "Leven Labs, Inc. DBA Admiral", "recessrain.com": "Leven Labs, Inc. DBA Admiral", + "reconditeprison.com": "Leven Labs, Inc. DBA Admiral", "reconditerake.com": "Leven Labs, Inc. DBA Admiral", "reconditerespect.com": "Leven Labs, Inc. DBA Admiral", "reflectivestatement.com": "Leven Labs, Inc. DBA Admiral", @@ -49890,6 +50635,7 @@ "resonantbrush.com": "Leven Labs, Inc. DBA Admiral", "resonantrock.com": "Leven Labs, Inc. DBA Admiral", "respectrain.com": "Leven Labs, Inc. DBA Admiral", + "resplendentecho.com": "Leven Labs, Inc. DBA Admiral", "restrainstorm.com": "Leven Labs, Inc. DBA Admiral", "restructureinvention.com": "Leven Labs, Inc. DBA Admiral", "retrievemint.com": "Leven Labs, Inc. DBA Admiral", @@ -49913,6 +50659,7 @@ "savoryorange.com": "Leven Labs, Inc. DBA Admiral", "scarceshock.com": "Leven Labs, Inc. DBA Admiral", "scaredcomfort.com": "Leven Labs, Inc. DBA Admiral", + "scaredslip.com": "Leven Labs, Inc. DBA Admiral", "scaredsnake.com": "Leven Labs, Inc. DBA Admiral", "scaredsnakes.com": "Leven Labs, Inc. DBA Admiral", "scaredsong.com": "Leven Labs, Inc. DBA Admiral", @@ -49938,6 +50685,8 @@ "selectivesummer.com": "Leven Labs, Inc. DBA Admiral", "selfishsnake.com": "Leven Labs, Inc. DBA Admiral", "separatesort.com": "Leven Labs, Inc. DBA Admiral", + "seraphicjubilee.com": "Leven Labs, Inc. DBA Admiral", + "serenepebble.com": "Leven Labs, Inc. DBA Admiral", "serioussuit.com": "Leven Labs, Inc. DBA Admiral", "serpentshampoo.com": "Leven Labs, Inc. DBA Admiral", "settleshoes.com": "Leven Labs, Inc. DBA Admiral", @@ -49977,12 +50726,14 @@ "smoggysongs.com": "Leven Labs, Inc. DBA Admiral", "sneakwind.com": "Leven Labs, Inc. DBA Admiral", "soggysponge.com": "Leven Labs, Inc. DBA Admiral", + "soggyzoo.com": "Leven Labs, Inc. DBA Admiral", "solarislabyrinth.com": "Leven Labs, Inc. DBA Admiral", "somberscarecrow.com": "Leven Labs, Inc. DBA Admiral", "sombersticks.com": "Leven Labs, Inc. DBA Admiral", "songsterritory.com": "Leven Labs, Inc. DBA Admiral", "soothingglade.com": "Leven Labs, Inc. DBA Admiral", "sordidsmile.com": "Leven Labs, Inc. DBA Admiral", + "soresidewalk.com": "Leven Labs, Inc. DBA Admiral", "soretrain.com": "Leven Labs, Inc. DBA Admiral", "sortsail.com": "Leven Labs, Inc. DBA Admiral", "sortsummer.com": "Leven Labs, Inc. DBA Admiral", @@ -50029,6 +50780,7 @@ "stretchsister.com": "Leven Labs, Inc. DBA Admiral", "stretchsneeze.com": "Leven Labs, Inc. DBA Admiral", "stretchsquirrel.com": "Leven Labs, Inc. DBA Admiral", + "stripedbat.com": "Leven Labs, Inc. DBA Admiral", "strivesidewalk.com": "Leven Labs, Inc. DBA Admiral", "strivesquirrel.com": "Leven Labs, Inc. DBA Admiral", "strokesystem.com": "Leven Labs, Inc. DBA Admiral", @@ -50065,6 +50817,7 @@ "tendertest.com": "Leven Labs, Inc. DBA Admiral", "terriblethumb.com": "Leven Labs, Inc. DBA Admiral", "terrifictooth.com": "Leven Labs, Inc. DBA Admiral", + "thingstaste.com": "Leven Labs, Inc. DBA Admiral", "thinkitten.com": "Leven Labs, Inc. DBA Admiral", "thirdrespect.com": "Leven Labs, Inc. DBA Admiral", "thomastorch.com": "Leven Labs, Inc. DBA Admiral", @@ -50075,7 +50828,9 @@ "tiredthroat.com": "Leven Labs, Inc. DBA Admiral", "tiresomethunder.com": "Leven Labs, Inc. DBA Admiral", "tradetooth.com": "Leven Labs, Inc. DBA Admiral", + "tranquilcan.com": "Leven Labs, Inc. DBA Admiral", "tranquilcanyon.com": "Leven Labs, Inc. DBA Admiral", + "tranquilplume.com": "Leven Labs, Inc. DBA Admiral", "tremendousearthquake.com": "Leven Labs, Inc. DBA Admiral", "tremendousplastic.com": "Leven Labs, Inc. DBA Admiral", "tritebadge.com": "Leven Labs, Inc. DBA Admiral", @@ -50104,12 +50859,19 @@ "unwieldyimpulse.com": "Leven Labs, Inc. DBA Admiral", "unwieldyplastic.com": "Leven Labs, Inc. DBA Admiral", "uselesslumber.com": "Leven Labs, Inc. DBA Admiral", + "vanishmemory.com": "Leven Labs, Inc. DBA Admiral", + "velvetquasar.com": "Leven Labs, Inc. DBA Admiral", "vengefulgrass.com": "Leven Labs, Inc. DBA Admiral", + "venomousvessel.com": "Leven Labs, Inc. DBA Admiral", "venusgloria.com": "Leven Labs, Inc. DBA Admiral", "verdantanswer.com": "Leven Labs, Inc. DBA Admiral", + "verdantloom.com": "Leven Labs, Inc. DBA Admiral", "verseballs.com": "Leven Labs, Inc. DBA Admiral", + "vibrantgale.com": "Leven Labs, Inc. DBA Admiral", "vibranthaven.com": "Leven Labs, Inc. DBA Admiral", + "vibranttalisman.com": "Leven Labs, Inc. DBA Admiral", "virtualvincent.com": "Leven Labs, Inc. DBA Admiral", + "vividmeadow.com": "Leven Labs, Inc. DBA Admiral", "volatileprofit.com": "Leven Labs, Inc. DBA Admiral", "volatilevessel.com": "Leven Labs, Inc. DBA Admiral", "voraciousgrip.com": "Leven Labs, Inc. DBA Admiral", @@ -50118,13 +50880,18 @@ "warmquiver.com": "Leven Labs, Inc. DBA Admiral", "wearbasin.com": "Leven Labs, Inc. DBA Admiral", "wellgroomedhydrant.com": "Leven Labs, Inc. DBA Admiral", + "whimsicalcanyon.com": "Leven Labs, Inc. DBA Admiral", "whimsicalgrove.com": "Leven Labs, Inc. DBA Admiral", "whisperingcascade.com": "Leven Labs, Inc. DBA Admiral", + "whisperingquasar.com": "Leven Labs, Inc. DBA Admiral", "whisperingsummit.com": "Leven Labs, Inc. DBA Admiral", "whispermeeting.com": "Leven Labs, Inc. DBA Admiral", "wildcommittee.com": "Leven Labs, Inc. DBA Admiral", + "wistfulwaste.com": "Leven Labs, Inc. DBA Admiral", "workoperation.com": "Leven Labs, Inc. DBA Admiral", + "wretchedfloor.com": "Leven Labs, Inc. DBA Admiral", "wrongwound.com": "Leven Labs, Inc. DBA Admiral", + "zephyrlabyrinth.com": "Leven Labs, Inc. DBA Admiral", "zestycrime.com": "Leven Labs, Inc. DBA Admiral", "zipperxray.com": "Leven Labs, Inc. DBA Admiral", "zlp6s.pw": "Leven Labs, Inc. DBA Admiral", diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b16297a76b..497830fdef 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8230,7 +8230,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8267,7 +8267,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8359,7 +8359,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8387,7 +8387,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8537,7 +8537,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8563,7 +8563,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8628,7 +8628,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8663,7 +8663,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8697,7 +8697,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8728,7 +8728,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9015,7 +9015,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9046,7 +9046,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9075,7 +9075,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9109,7 +9109,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9140,7 +9140,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9173,11 +9173,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9415,7 +9415,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9442,7 +9442,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9475,7 +9475,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9513,7 +9513,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9549,7 +9549,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9584,11 +9584,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9762,11 +9762,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9795,10 +9795,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index c7c1d02b51..7999c82620 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.108.0 + 7.109.0 Key version Title From 85a08dd0ebb67ca5dea4ac7560a5d339eccd9523 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Mon, 12 Feb 2024 19:13:24 -0800 Subject: [PATCH 028/245] Increase the tunnel monitor check interval to 1 minute (#2467) Task/Issue URL: https://app.asana.com/0/1203137811378537/1206567515074116/f Tech Design URL: CC: Description: Client PR for duckduckgo/BrowserServicesKit#660. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b16297a76b..bb1472bf42 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10005,7 +10005,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 106.0.0; + version = 106.0.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1ba6be112c..7e74a98bef 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "f9cf9ae340e918042d59c128bf83d0924e5d4e85", - "version" : "106.0.0" + "revision" : "3f5e33ec3d75dd2c130cfc6915c0a6e8efeb96f1", + "version" : "106.0.1" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 0e8afdefb8..9765ab738d 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 873f398e8b..aa8c6af19b 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index c299bcce08..2d0a802dba 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 3fba35550138adab6374215b817e9d00de7b4b2e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 13 Feb 2024 13:53:38 +0100 Subject: [PATCH 029/245] Subscriptions > Personal info Removal (#2468) Task/Issue URL: https://app.asana.com/0/72649045549333/1205043809052693/f Description: Adds links to MacOS and Windows browser dowload from Personal Information renewal Removes all waitlist functionality related to macOS and Windows browser. (Use a simple SwiftUI View instead as there's no waitlist anymore) --- DuckDuckGo.xcodeproj/project.pbxproj | 86 ++--- DuckDuckGo/AppDelegate+Waitlists.swift | 12 - DuckDuckGo/AppDelegate.swift | 9 - DuckDuckGo/Debug.storyboard | 121 +----- .../DesktopDownloadPlatformConstants.swift | 84 +++++ ...tViews.swift => DesktopDownloadView.swift} | 152 ++++---- .../DesktopDownloadViewButtonStyle.swift | 67 ++++ DuckDuckGo/DesktopDownloadViewModel.swift | 50 +++ DuckDuckGo/Info.plist | 1 - DuckDuckGo/MacBrowserWaitlist.swift | 54 --- DuckDuckGo/MacBrowserWaitlistView.swift | 72 ---- DuckDuckGo/MacWaitlistViewController.swift | 135 ------- DuckDuckGo/SettingsDebugView.swift | 2 +- DuckDuckGo/SettingsGeneralView.swift | 1 + DuckDuckGo/SettingsLegacyViewProvider.swift | 4 - DuckDuckGo/SettingsMoreView.swift | 18 +- DuckDuckGo/SettingsSubscriptionView.swift | 11 +- DuckDuckGo/SettingsViewModel.swift | 2 - .../Contents.json | 15 + .../Information-Remover-320.pdf | Bin 0 -> 245732 bytes .../Platform-Apple-16.imageset/Contents.json | 15 + .../Platform-Apple-16.svg | 10 + .../Contents.json | 15 + .../Platform-Windows-16.svg | 3 + .../ViewModel/SubscriptionPIRViewModel.swift | 30 ++ .../Views/SubscriptionPIRView.swift | 201 ++++++++++ DuckDuckGo/UserText.swift | 45 +-- DuckDuckGo/WindowsBrowserWaitlist.swift | 99 ----- ...wsBrowserWaitlistDebugViewController.swift | 190 ---------- DuckDuckGo/WindowsBrowserWaitlistView.swift | 348 ------------------ .../WindowsWaitlistViewController.swift | 196 ---------- DuckDuckGo/en.lproj/Localizable.strings | 59 +-- .../WindowsBrowserWaitlistTests.swift | 71 ---- .../DuckUI/Sources/DuckUI/Color.swift | 2 +- 34 files changed, 651 insertions(+), 1529 deletions(-) create mode 100644 DuckDuckGo/DesktopDownloadPlatformConstants.swift rename DuckDuckGo/{WaitlistViews.swift => DesktopDownloadView.swift} (54%) create mode 100644 DuckDuckGo/DesktopDownloadViewButtonStyle.swift create mode 100644 DuckDuckGo/DesktopDownloadViewModel.swift delete mode 100644 DuckDuckGo/MacBrowserWaitlist.swift delete mode 100644 DuckDuckGo/MacBrowserWaitlistView.swift delete mode 100644 DuckDuckGo/MacWaitlistViewController.swift create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Information-Remover-320.pdf create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Platform-Apple-16.svg create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg create mode 100644 DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift create mode 100644 DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift delete mode 100644 DuckDuckGo/WindowsBrowserWaitlist.swift delete mode 100644 DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift delete mode 100644 DuckDuckGo/WindowsBrowserWaitlistView.swift delete mode 100644 DuckDuckGo/WindowsWaitlistViewController.swift delete mode 100644 DuckDuckGoTests/WindowsBrowserWaitlistTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bb1472bf42..5a4dbfe3b6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -257,9 +257,6 @@ 37DF000F29F9D635002B7D3E /* SyncBookmarksAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37DF000E29F9D635002B7D3E /* SyncBookmarksAdapter.swift */; }; 37E615752A5F533E00ACD63D /* SyncCredentialsAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37E615742A5F533E00ACD63D /* SyncCredentialsAdapter.swift */; }; 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAAA29911BF1000E420A /* WaitlistExtensions.swift */; }; - 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAB129914232000E420A /* WindowsBrowserWaitlistView.swift */; }; - 37FCAAB429914C77000E420A /* WindowsWaitlistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAB329914C77000E420A /* WindowsWaitlistViewController.swift */; }; - 37FCAAB629919CEB000E420A /* WindowsBrowserWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAAB529919CEB000E420A /* WindowsBrowserWaitlist.swift */; }; 37FCAABC2992F592000E420A /* MultilineScrollableTextFix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABB2992F592000E420A /* MultilineScrollableTextFix.swift */; }; 37FCAAC029930E26000E420A /* FailedAssertionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FCAABF29930E26000E420A /* FailedAssertionView.swift */; }; 37FD780F2A29E28B00B36DB1 /* SyncErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FD780E2A29E28B00B36DB1 /* SyncErrorHandler.swift */; }; @@ -278,12 +275,7 @@ 4B60AC97252EC07B00E8D219 /* fullscreenvideo.js in Resources */ = {isa = PBXBuildFile; fileRef = 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */; }; 4B60ACA1252EC0B100E8D219 /* FullScreenVideoUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */; }; 4B62C4BA25B930DD008912C6 /* AppConfigurationFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */; }; - 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E027FD1E340050A7A1 /* MacBrowserWaitlistView.swift */; }; - 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E327FD1E340050A7A1 /* MacBrowserWaitlist.swift */; }; - 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E427FD1E340050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift */; }; - 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E527FD1E340050A7A1 /* MacWaitlistViewController.swift */; }; 4B6484F327FD1E350050A7A1 /* MenuControllerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484E927FD1E340050A7A1 /* MenuControllerView.swift */; }; - 4B6484FC27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6484FB27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift */; }; 4B75EA9226A266CB00018634 /* PrintingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B75EA9126A266CB00018634 /* PrintingUserScript.swift */; }; 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B78074D2B183A1F009DB2CF /* SurveyURLBuilder.swift */; }; 4B83396C29AC0701003F7EA9 /* AppTrackingProtectionStoringModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B470EE2299C6DD10086EBDC /* AppTrackingProtectionStoringModel.swift */; }; @@ -315,7 +307,6 @@ 4BEF656D2989C2FC00B650CB /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D307E2989C0C800918636 /* EventType.swift */; }; 4BEF656E2989C2FC00B650CB /* ProxySocketEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 021D307B2989C0C600918636 /* ProxySocketEvent.swift */; }; 4BFB911B29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */; }; - 56244C1D2A137B1900EDF259 /* WaitlistViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */; }; 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; }; 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; @@ -797,6 +788,8 @@ D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; + D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; + D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8B2B291CA90054390C /* URL+Subscription.swift */; }; D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */; }; D6D12CA12B291CA90054390C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8D2B291CA90054390C /* Logging.swift */; }; @@ -813,6 +806,10 @@ D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; }; D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; + D6E0C1832B7A2B1E00D5E1E9 /* DesktopDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */; }; + D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */; }; + D6E0C1872B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1862B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift */; }; + D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1882B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift */; }; D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */; }; D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */; }; D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C302B1EA309006C8AFB /* SettingsCell.swift */; }; @@ -1363,9 +1360,6 @@ 37DF000E29F9D635002B7D3E /* SyncBookmarksAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncBookmarksAdapter.swift; sourceTree = ""; }; 37E615742A5F533E00ACD63D /* SyncCredentialsAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncCredentialsAdapter.swift; sourceTree = ""; }; 37FCAAAA29911BF1000E420A /* WaitlistExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistExtensions.swift; sourceTree = ""; }; - 37FCAAB129914232000E420A /* WindowsBrowserWaitlistView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlistView.swift; sourceTree = ""; }; - 37FCAAB329914C77000E420A /* WindowsWaitlistViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsWaitlistViewController.swift; sourceTree = ""; }; - 37FCAAB529919CEB000E420A /* WindowsBrowserWaitlist.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlist.swift; sourceTree = ""; }; 37FCAABB2992F592000E420A /* MultilineScrollableTextFix.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineScrollableTextFix.swift; sourceTree = ""; }; 37FCAABF29930E26000E420A /* FailedAssertionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedAssertionView.swift; sourceTree = ""; }; 37FCAACB2993149A000E420A /* Waitlist */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Waitlist; sourceTree = ""; }; @@ -1383,12 +1377,7 @@ 4B60AC96252EC07B00E8D219 /* fullscreenvideo.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = fullscreenvideo.js; sourceTree = ""; }; 4B60ACA0252EC0B100E8D219 /* FullScreenVideoUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenVideoUserScript.swift; sourceTree = ""; }; 4B62C4B925B930DD008912C6 /* AppConfigurationFetchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationFetchTests.swift; sourceTree = ""; }; - 4B6484E027FD1E340050A7A1 /* MacBrowserWaitlistView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacBrowserWaitlistView.swift; sourceTree = ""; }; - 4B6484E327FD1E340050A7A1 /* MacBrowserWaitlist.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacBrowserWaitlist.swift; sourceTree = ""; }; - 4B6484E427FD1E340050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlistDebugViewController.swift; sourceTree = ""; }; - 4B6484E527FD1E340050A7A1 /* MacWaitlistViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacWaitlistViewController.swift; sourceTree = ""; }; 4B6484E927FD1E340050A7A1 /* MenuControllerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MenuControllerView.swift; sourceTree = ""; }; - 4B6484FB27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowsBrowserWaitlistTests.swift; sourceTree = ""; }; 4B75EA9126A266CB00018634 /* PrintingUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrintingUserScript.swift; sourceTree = ""; }; 4B78074B2B1823C5009DB2CF /* VPNWaitlistActivationDateStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNWaitlistActivationDateStore.swift; sourceTree = ""; }; 4B78074D2B183A1F009DB2CF /* SurveyURLBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurveyURLBuilder.swift; sourceTree = ""; }; @@ -1410,7 +1399,6 @@ 4BCD146C2B05DB09000B1E4C /* NetworkProtectionAccessControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionAccessControllerTests.swift; sourceTree = ""; }; 4BE27566272F878F006B20B0 /* URLRequestExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLRequestExtension.swift; path = ../DuckDuckGo/URLRequestExtension.swift; sourceTree = ""; }; 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionStoringModelPerformanceTests.swift; sourceTree = ""; }; - 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistViews.swift; sourceTree = ""; }; 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimator.swift; sourceTree = ""; }; 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimatorTests.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; @@ -2461,6 +2449,8 @@ D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TopMostController.swift"; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; + D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; + D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; D6D12C8B2B291CA90054390C /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPurchaseEnvironment.swift; sourceTree = ""; }; D6D12C8D2B291CA90054390C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; @@ -2477,6 +2467,10 @@ D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; + D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadView.swift; sourceTree = ""; }; + D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadPlatformConstants.swift; sourceTree = ""; }; + D6E0C1862B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadViewButtonStyle.swift; sourceTree = ""; }; + D6E0C1882B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadViewModel.swift; sourceTree = ""; }; D6E83C112B1E6AB3006C8AFB /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; D6E83C2D2B1EA06E006C8AFB /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; D6E83C302B1EA309006C8AFB /* SettingsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsCell.swift; sourceTree = ""; }; @@ -3483,27 +3477,6 @@ name = Sync; sourceTree = ""; }; - 37FCAAA0299117F9000E420A /* MacBrowser */ = { - isa = PBXGroup; - children = ( - 4B6484E327FD1E340050A7A1 /* MacBrowserWaitlist.swift */, - 4B6484E027FD1E340050A7A1 /* MacBrowserWaitlistView.swift */, - 4B6484E527FD1E340050A7A1 /* MacWaitlistViewController.swift */, - ); - name = MacBrowser; - sourceTree = ""; - }; - 37FCAAA129911801000E420A /* WindowsBrowser */ = { - isa = PBXGroup; - children = ( - 37FCAAB529919CEB000E420A /* WindowsBrowserWaitlist.swift */, - 37FCAAB129914232000E420A /* WindowsBrowserWaitlistView.swift */, - 37FCAAB329914C77000E420A /* WindowsWaitlistViewController.swift */, - 4B6484E427FD1E340050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift */, - ); - name = WindowsBrowser; - sourceTree = ""; - }; 4B274F5E2AFEAEB3003F0745 /* Widget */ = { isa = PBXGroup; children = ( @@ -3539,9 +3512,6 @@ isa = PBXGroup; children = ( 37FCAAAA29911BF1000E420A /* WaitlistExtensions.swift */, - 56244C1C2A137B1900EDF259 /* WaitlistViews.swift */, - 37FCAAA0299117F9000E420A /* MacBrowser */, - 37FCAAA129911801000E420A /* WindowsBrowser */, 4BBBBA882B031B3300D965DA /* VPN */, 8524AAAB2A3888FE00EEC6D2 /* Waitlist.xcassets */, ); @@ -3551,7 +3521,6 @@ 4B6484F927FFCF520050A7A1 /* Waitlist */ = { isa = PBXGroup; children = ( - 4B6484FB27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift */, ); name = Waitlist; sourceTree = ""; @@ -3777,6 +3746,7 @@ F1668BCC1E798025008CBA04 /* Bookmarks */, 9830A05725ED0C5D00DB64DE /* BrowsingMenu */, B652DF02287C01EE00C12A9C /* ContentBlocking */, + D6E0C1812B7A2B0700D5E1E9 /* DesktopDownloads */, 310D09192799EF5C00DC0060 /* Downloads */, F143C2C51E4A08F300CFDE3A /* DuckDuckGo.entitlements */, C159DF052A430B36007834BB /* EmailProtection */, @@ -4563,6 +4533,7 @@ D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */, D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, + D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -4584,6 +4555,7 @@ D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */, D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */, D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */, + D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */, D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */, ); @@ -4594,8 +4566,8 @@ isa = PBXGroup; children = ( D668D92C2B696945008E2FF2 /* Subscription.swift */, - D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, ); @@ -4667,6 +4639,17 @@ path = AsyncHeadlessWebview; sourceTree = ""; }; + D6E0C1812B7A2B0700D5E1E9 /* DesktopDownloads */ = { + isa = PBXGroup; + children = ( + D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */, + D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */, + D6E0C1862B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift */, + D6E0C1882B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift */, + ); + name = DesktopDownloads; + sourceTree = ""; + }; D6E83C322B1F1279006C8AFB /* Sections */ = { isa = PBXGroup; children = ( @@ -6659,6 +6642,7 @@ 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */, F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */, + D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */, D668D9252B693778008E2FF2 /* SubscriptionITPView.swift in Sources */, C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */, 982E5630222C3D5B008D861B /* FeedbackPickerViewController.swift in Sources */, @@ -6760,13 +6744,14 @@ CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, + D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */, + D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */, 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */, 8C4724502217A14B004C9B2D /* TabViewControllerLongPressBookmarkExtension.swift in Sources */, 1EDE39D22705D4A200C99C72 /* FileSizeDebugViewController.swift in Sources */, 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, - 4B6484EA27FD1E350050A7A1 /* MacBrowserWaitlistView.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */, @@ -6798,7 +6783,6 @@ CBD4F13D279EBFA000B20FD7 /* HomeMessageCollectionViewCell.swift in Sources */, 8505836D219F424500ED4EDB /* Point.swift in Sources */, 3158461A281B08F5004ADB8B /* AutofillLoginListViewModel.swift in Sources */, - 37FCAAB429914C77000E420A /* WindowsWaitlistViewController.swift in Sources */, 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */, 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */, D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */, @@ -6809,7 +6793,6 @@ D6E83C3A2B1F231A006C8AFB /* SettingsSyncView.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, - 4B6484EF27FD1E350050A7A1 /* MacWaitlistViewController.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, @@ -6853,6 +6836,7 @@ 311BD1B12836C0CA00AEF6C1 /* AutofillLoginListAuthenticator.swift in Sources */, 4BBBBA902B031B4200D965DA /* VPNWaitlist.swift in Sources */, B652DF13287C373A00C12A9C /* ScriptSourceProviding.swift in Sources */, + D6E0C1872B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift in Sources */, 854A012B2A54412600FCC628 /* ActivityViewController.swift in Sources */, F1CA3C391F045885005FADB3 /* PrivacyUserDefaults.swift in Sources */, BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */, @@ -6894,12 +6878,12 @@ 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */, 0290472E29E99A2F0008FE3C /* GenericIconView.swift in Sources */, + D6E0C1832B7A2B1E00D5E1E9 /* DesktopDownloadView.swift in Sources */, 1E7A71172934EB6400B7EA19 /* OmniBarNotificationAnimator.swift in Sources */, 85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */, F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */, 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, - 4B6484ED27FD1E350050A7A1 /* MacBrowserWaitlist.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, @@ -6907,10 +6891,8 @@ F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */, F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */, CBD4F13E279EBFAB00B20FD7 /* HomeMessageView.swift in Sources */, - 56244C1D2A137B1900EDF259 /* WaitlistViews.swift in Sources */, 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */, 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */, - 37FCAAB229914232000E420A /* WindowsBrowserWaitlistView.swift in Sources */, C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */, 0290472C29E8821E0008FE3C /* AppTPBreakageFormHeaderView.swift in Sources */, 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */, @@ -6927,7 +6909,6 @@ 027F48782A4B663C001A1C6C /* AppTPFAQView.swift in Sources */, D6E83C3D2B1F2C03006C8AFB /* SettingsLoginsView.swift in Sources */, 02A4EACA29B0F464009BE006 /* AppTPToggleViewModel.swift in Sources */, - 4B6484EE27FD1E350050A7A1 /* WindowsBrowserWaitlistDebugViewController.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, 1E7A711C2934EEBC00B7EA19 /* OmniBarNotification.swift in Sources */, @@ -6974,12 +6955,12 @@ EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, 1E865AF0272042DB001C74F3 /* TextSizeSettingsViewController.swift in Sources */, + D6E0C1892B7A2E0D00D5E1E9 /* DesktopDownloadViewModel.swift in Sources */, 8524CC9A246DA81700E59D45 /* FullscreenDaxDialogViewController.swift in Sources */, F17669D71E43401C003D3222 /* MainViewController.swift in Sources */, 984D60B2222A1284003B9E3B /* FeedbackFormViewController.swift in Sources */, 31A42564285A09E800049386 /* FaviconView.swift in Sources */, 85374D3821AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift in Sources */, - 37FCAAB629919CEB000E420A /* WindowsBrowserWaitlist.swift in Sources */, 98E888F2223FCC4A00B608A4 /* OnboardingViewController.swift in Sources */, C1B7B51C28941E980098FD6A /* HomeMessageViewModelBuilder.swift in Sources */, 85BA58551F34F49E00C6E8CA /* AppUserDefaults.swift in Sources */, @@ -7113,7 +7094,6 @@ 98983096255B5019003339A2 /* BookmarksCachingSearchTests.swift in Sources */, EE7917912A83DE93008DFF28 /* CombineTestUtilities.swift in Sources */, 85480CB429226B3B007E8F13 /* CrashCollectionExtensionTests.swift in Sources */, - 4B6484FC27FFD14F0050A7A1 /* WindowsBrowserWaitlistTests.swift in Sources */, 8540BD5223D8C2220057FDD2 /* PreserveLoginsTests.swift in Sources */, 85F200072217032E006BB258 /* AddressDisplayHelperTests.swift in Sources */, B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 5c1c37c51f..db54ce6826 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -34,7 +34,6 @@ extension AppDelegate { } func checkWaitlists() { - checkWindowsWaitlist() #if NETWORK_PROTECTION checkNetworkProtectionWaitlist() @@ -43,13 +42,6 @@ extension AppDelegate { } - private func checkWindowsWaitlist() { - WindowsBrowserWaitlist.shared.fetchInviteCodeIfAvailable { error in - guard error == nil else { return } - WindowsBrowserWaitlist.shared.sendInviteCodeAvailableNotification() - } - } - #if NETWORK_PROTECTION private func checkNetworkProtectionWaitlist() { let accessController = NetworkProtectionAccessController() @@ -96,10 +88,6 @@ extension AppDelegate { private func checkWaitlistBackgroundTasks() { BGTaskScheduler.shared.getPendingTaskRequests { tasks in - let hasWindowsBrowserWaitlistTask = tasks.contains { $0.identifier == WindowsBrowserWaitlist.backgroundRefreshTaskIdentifier } - if !hasWindowsBrowserWaitlistTask { - WindowsBrowserWaitlist.shared.scheduleBackgroundRefreshTask() - } #if NETWORK_PROTECTION let hasVPNWaitlistTask = tasks.contains { $0.identifier == VPNWaitlist.backgroundRefreshTaskIdentifier } diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index bc1a067368..f84dedc923 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -290,7 +290,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Task handler registration needs to happen before the end of `didFinishLaunching`, otherwise submitting a task can throw an exception. // Having both in `didBecomeActive` can sometimes cause the exception when running on a physical device, so registration happens here. AppConfigurationFetch.registerBackgroundRefreshTaskHandler() - WindowsBrowserWaitlist.shared.registerBackgroundRefreshTaskHandler() #if NETWORK_PROTECTION VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() @@ -805,9 +804,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate { withCompletionHandler completionHandler: @escaping () -> Void) { if response.actionIdentifier == UNNotificationDefaultActionIdentifier { let identifier = response.notification.request.identifier - if identifier == WindowsBrowserWaitlist.notificationIdentifier { - presentWindowsBrowserWaitlistSettingsModal() - } #if NETWORK_PROTECTION if NetworkProtectionNotificationIdentifier(rawValue: identifier) != nil { @@ -823,11 +819,6 @@ extension AppDelegate: UNUserNotificationCenterDelegate { completionHandler() } - - private func presentWindowsBrowserWaitlistSettingsModal() { - let waitlistViewController = WindowsWaitlistViewController(nibName: nil, bundle: nil) - presentSettings(with: waitlistViewController) - } #if NETWORK_PROTECTION private func presentNetworkProtectionWaitlistModal() { diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index b359cd0648..ecddf0d91a 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -101,28 +101,8 @@ - - - - - - - - - - - - - - - + @@ -142,7 +122,7 @@ - + @@ -162,7 +142,7 @@ - + @@ -182,7 +162,7 @@ - + @@ -202,7 +182,7 @@ - + @@ -222,7 +202,7 @@ - + @@ -242,7 +222,7 @@ - + @@ -251,7 +231,7 @@ - + @@ -260,7 +240,7 @@ - + @@ -269,7 +249,7 @@ - + @@ -278,7 +258,7 @@ - + @@ -287,7 +267,7 @@ - + @@ -631,69 +611,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -879,17 +796,17 @@ - + - + - + - + diff --git a/DuckDuckGo/DesktopDownloadPlatformConstants.swift b/DuckDuckGo/DesktopDownloadPlatformConstants.swift new file mode 100644 index 0000000000..15cfea36ae --- /dev/null +++ b/DuckDuckGo/DesktopDownloadPlatformConstants.swift @@ -0,0 +1,84 @@ +// +// DesktopDownloadPlatformConstants.swift +// DuckDuckGo +// +// 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. +// + +enum DesktopDownloadPlatform { + case windows + case mac +} + +struct DesktopDownloadPlatformConstants { + let platform: DesktopDownloadPlatform + + var imageName: String { + switch platform { + case .windows: + return "WindowsWaitlistJoinWaitlist" + case .mac: + return "WaitlistMacComputer" + } + } + var title: String { + switch platform { + case .windows: + return UserText.windowsWaitlistTryDuckDuckGoForWindowsDownload + case .mac: + return UserText.macWaitlistTryDuckDuckGoForMac + } + } + var summary: String { + switch platform { + case .windows: + return UserText.windowsWaitlistSummary + case .mac: + return UserText.macWaitlistSummary + } + } + var onYourString: String { + switch platform { + case .windows: + return UserText.windowsWaitlistOnYourComputerGoTo + case .mac: + return UserText.macWaitlistOnYourMacGoTo + } + } + var downloadURL: String { + switch platform { + case .windows: + return "duckduckgo.com/windows" + case .mac: + return "duckduckgo.com/mac" + } + } + var otherPlatformText: String { + switch platform { + case .windows: + return UserText.windowsWaitlistMac + case .mac: + return UserText.macWaitlistWindows + } + } + var viewTitle: String { + switch platform { + case .windows: + return UserText.windowsWaitlistTitle + case .mac: + return UserText.macBrowserTitle + } + } +} diff --git a/DuckDuckGo/WaitlistViews.swift b/DuckDuckGo/DesktopDownloadView.swift similarity index 54% rename from DuckDuckGo/WaitlistViews.swift rename to DuckDuckGo/DesktopDownloadView.swift index 0ced5a5e9f..f089c1b21b 100644 --- a/DuckDuckGo/WaitlistViews.swift +++ b/DuckDuckGo/DesktopDownloadView.swift @@ -1,8 +1,8 @@ // -// WaitlistViews.swift +// DesktopDownloadView.swift // DuckDuckGo // -// Copyright © 2023 DuckDuckGo. All rights reserved. +// 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. @@ -17,20 +17,14 @@ // limitations under the License. // +import Foundation import SwiftUI -import Waitlist -struct WaitlistDownloadBrowserContentView: View { - - let action: WaitlistViewActionHandler - let constants: BrowserDownloadLinkConstants - - init(platform: BrowserDownloadLink, action: @escaping WaitlistViewActionHandler) { - self.action = action - self.constants = BrowserDownloadLinkConstants(platform: platform) - } +struct DesktopDownloadView: View { + @StateObject var viewModel: DesktopDownloadViewModel @State private var shareButtonFrame: CGRect = .zero + @State private var isShareSheetVisible = false let padding = UIDevice.current.localizedModel == "iPad" ? 100.0 : 0.0 @@ -38,41 +32,38 @@ struct WaitlistDownloadBrowserContentView: View { GeometryReader { proxy in ScrollView { VStack(alignment: .center, spacing: 8) { - HeaderView(imageName: constants.imageName, title: constants.title) + headerView - Text(constants.summary) + Text(viewModel.browserDetails.summary) .daxBodyRegular() .foregroundColor(.waitlistTextSecondary) .multilineTextAlignment(.center) .lineSpacing(6) .padding(.horizontal, padding) - Text(constants.onYourString) + Text(viewModel.browserDetails.onYourString) .daxBodyRegular() .foregroundColor(.waitlistTextSecondary) .multilineTextAlignment(.center) .lineSpacing(6) .padding(.top, 18) - - Text(constants.downloadURL) + + menuView .daxHeadline() .foregroundColor(.waitlistBlue) - .menuController(UserText.macWaitlistCopy) { - action(.copyDownloadURLToPasteboard) - } .fixedSize() - + Button( action: { - action(.openShareSheet(shareButtonFrame)) + self.isShareSheetVisible = true }, label: { HStack { Image("Share-16") - Text(UserText.macWaitlistShareLink) + Text(viewModel.browserDetails.downloadURL) } } ) - .buttonStyle(RoundedButtonStyle(enabled: true)) + .buttonStyle(DesktopDownloadViewButtonStyle(enabled: true)) .padding(.horizontal, padding) .padding(.top, 24) .background( @@ -86,14 +77,19 @@ struct WaitlistDownloadBrowserContentView: View { self.shareButtonFrame = newFrame } } + .sheet(isPresented: $isShareSheetVisible) { + DesktopDownloadShareSheet(items: [viewModel.downloadURL]) + } Spacer(minLength: 24) Button( action: { - action(.custom(constants.customAction)) + withAnimation { + viewModel.switchPlatform() + } }, label: { - Text(constants.otherPlatformText) + Text(viewModel.browserDetails.otherPlatformText) .daxHeadline() .foregroundColor(.waitlistBlue) .multilineTextAlignment(.center) @@ -106,6 +102,42 @@ struct WaitlistDownloadBrowserContentView: View { .padding([.leading, .trailing], 24) .frame(minHeight: proxy.size.height) } + .navigationTitle(viewModel.browserDetails.viewTitle) + } + } + + @ViewBuilder + private var headerView: some View { + VStack(spacing: 18) { + Image(viewModel.browserDetails.imageName) + + Text(viewModel.browserDetails.title) + .daxTitle2() + .foregroundColor(.waitlistTextPrimary) + .lineSpacing(6) + .multilineTextAlignment(.center) + .fixMultilineScrollableText() + } + .padding(.top, 24) + .padding(.bottom, 12) + } + + @ViewBuilder + private var menuView: some View { + + // The .menuController modifier prevents the Text view from + // updating when viewModel.browserDetails.downloadURL changes + // so this is a hack to render another view + if viewModel.browserDetails.platform == .mac { + Text(viewModel.browserDetails.downloadURL) + .menuController(UserText.macWaitlistCopy) { + viewModel.copyLink() + } + } else { + Text(viewModel.browserDetails.downloadURL) + .menuController(UserText.macWaitlistCopy) { + viewModel.copyLink() + } } } } @@ -115,68 +147,12 @@ private struct ShareButtonFramePreferenceKey: PreferenceKey { static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} } -enum BrowserDownloadLink { - case windows - case mac -} - -struct BrowserDownloadLinkConstants { - let platform: BrowserDownloadLink +struct DesktopDownloadShareSheet: UIViewControllerRepresentable { + var items: [Any] - var imageName: String { - switch platform { - case .windows: - return "WindowsWaitlistJoinWaitlist" - case .mac: - return "WaitlistMacComputer" - } - } - var title: String { - switch platform { - case .windows: - return UserText.windowsWaitlistTryDuckDuckGoForWindowsDownload - case .mac: - return UserText.macWaitlistTryDuckDuckGoForMac - } - } - var summary: String { - switch platform { - case .windows: - return UserText.windowsWaitlistSummary - case .mac: - return UserText.macWaitlistSummary - } - } - var onYourString: String { - switch platform { - case .windows: - return UserText.windowsWaitlistOnYourComputerGoTo - case .mac: - return UserText.macWaitlistOnYourMacGoTo - } - } - var downloadURL: String { - switch platform { - case .windows: - return "duckduckgo.com/windows" - case .mac: - return "duckduckgo.com/mac" - } - } - var customAction: WaitlistViewModel.ViewCustomAction { - switch platform { - case .windows: - return .openMacBrowserWaitlist - case .mac: - return .openWindowsBrowserWaitlist - } - } - var otherPlatformText: String { - switch platform { - case .windows: - return UserText.windowsWaitlistMac - case .mac: - return UserText.macWaitlistWindows - } + func makeUIViewController(context: Context) -> UIActivityViewController { + UIActivityViewController(activityItems: items, applicationActivities: nil) } + + func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) {} } diff --git a/DuckDuckGo/DesktopDownloadViewButtonStyle.swift b/DuckDuckGo/DesktopDownloadViewButtonStyle.swift new file mode 100644 index 0000000000..42a50ad708 --- /dev/null +++ b/DuckDuckGo/DesktopDownloadViewButtonStyle.swift @@ -0,0 +1,67 @@ +// +// DesktopDownloadViewButtonStyle.swift +// DuckDuckGo +// +// 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 SwiftUI + +public struct DesktopDownloadViewButtonStyle: ButtonStyle { + + public enum Style { + case solid + case bordered + } + + public let enabled: Bool + private let style: Style + + public init(enabled: Bool, style: Style = .solid) { + self.enabled = enabled + self.style = style + } + + public func makeBody(configuration: Self.Configuration) -> some View { + let backgroundColor: Color + let foregroundColor: Color + let borderColor: Color + let borderWidth: CGFloat + + switch style { + case .solid: + backgroundColor = enabled ? Color.waitlistBlue : Color.waitlistBlue.opacity(0.2) + foregroundColor = Color.waitlistButtonText + borderColor = Color.clear + borderWidth = 0 + case .bordered: + backgroundColor = Color.clear + foregroundColor = Color.waitlistBlue + borderColor = Color.waitlistBlue + borderWidth = 2 + } + + return configuration.label + .daxHeadline() + .frame(maxWidth: .infinity) + .padding([.top, .bottom], 16) + .background(backgroundColor) + .foregroundColor(foregroundColor) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .overlay(RoundedRectangle(cornerRadius: 8).strokeBorder(borderColor, lineWidth: borderWidth)) + } + +} diff --git a/DuckDuckGo/DesktopDownloadViewModel.swift b/DuckDuckGo/DesktopDownloadViewModel.swift new file mode 100644 index 0000000000..6d52883345 --- /dev/null +++ b/DuckDuckGo/DesktopDownloadViewModel.swift @@ -0,0 +1,50 @@ +// +// DesktopDownloadViewModel.swift +// DuckDuckGo +// +// 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 UIKit + +final class DesktopDownloadViewModel: ObservableObject { + + static let defaultURL = URL(string: "https://duckduckgo.com/")! + static let prefix = "https://" + + private var platform: DesktopDownloadPlatform + @Published var browserDetails: DesktopDownloadPlatformConstants + + var downloadURL: URL { + guard let url = URL(string: "\(Self.prefix)\(browserDetails.downloadURL)") else { return Self.defaultURL } + return url + } + + init(platform: DesktopDownloadPlatform) { + self.platform = platform + self.browserDetails = .init(platform: platform) + } + + func copyLink() { + UIPasteboard.general.url = downloadURL + } + + func switchPlatform() { + self.platform = (platform == .mac) ? .windows : .mac + self.browserDetails = .init(platform: platform) + } + +} diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 94ad683aca..b61e407988 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -5,7 +5,6 @@ BGTaskSchedulerPermittedIdentifiers com.duckduckgo.app.vpnWaitlistStatus - com.duckduckgo.app.windowsBrowserWaitlistStatus com.duckduckgo.app.configurationRefresh com.duckduckgo.app.remoteMessageRefresh diff --git a/DuckDuckGo/MacBrowserWaitlist.swift b/DuckDuckGo/MacBrowserWaitlist.swift deleted file mode 100644 index 0779de05b9..0000000000 --- a/DuckDuckGo/MacBrowserWaitlist.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// MacBrowserWaitlist.swift -// DuckDuckGo -// -// Copyright © 2022 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 Core -import Waitlist - -struct MacBrowserWaitlist: Waitlist { - - let isAvailable: Bool = true - - static let identifier: String = "mac" - let isWaitlistRemoved: Bool = true - static let apiProductName: String = "macosbrowser" - static let downloadURL: URL = URL.mac - - static let shared: MacBrowserWaitlist = .init() - - static let backgroundTaskName = "Mac Browser Waitlist Status Task" - static let backgroundRefreshTaskIdentifier = "com.duckduckgo.app.macBrowserWaitlistStatus" - static let notificationIdentifier = "com.duckduckgo.ios.mac-browser.invite-code-available" - static let inviteAvailableNotificationTitle = UserText.macWaitlistAvailableNotificationTitle - static let inviteAvailableNotificationBody = UserText.waitlistAvailableNotificationBody - - let settingsSubtitle: String = UserText.macWaitlistBrowsePrivately - - let waitlistStorage: WaitlistStorage - let waitlistRequest: WaitlistRequest - - init(store: WaitlistStorage, request: WaitlistRequest) { - self.waitlistStorage = store - self.waitlistRequest = request - } -} - -extension WaitlistViewModel.ViewCustomAction { - static var openWindowsBrowserWaitlist = WaitlistViewModel.ViewCustomAction(identifier: "openWindowsBrowserWaitlist") -} diff --git a/DuckDuckGo/MacBrowserWaitlistView.swift b/DuckDuckGo/MacBrowserWaitlistView.swift deleted file mode 100644 index 5ef9077f28..0000000000 --- a/DuckDuckGo/MacBrowserWaitlistView.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// MacBrowserWaitlistView.swift -// DuckDuckGo -// -// Copyright © 2022 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 Waitlist -import DesignResourcesKit - -struct MacBrowserWaitlistView: View { - - @EnvironmentObject var viewModel: WaitlistViewModel - - var body: some View { - WaitlistDownloadBrowserContentView(platform: .mac) { action in - Task { await viewModel.perform(action: action) } - } - } - -} - -// MARK: - Previews - -private struct MacBrowserWaitlistView_Previews: PreviewProvider { - - static var previews: some View { - Group { - PreviewView("Mac Browser Beta") { - WaitlistDownloadBrowserContentView(platform: .mac) { _ in } - } - - if #available(iOS 15.0, *) { - WaitlistDownloadBrowserContentView(platform: .mac) { _ in } - .previewInterfaceOrientation(.landscapeLeft) - } - } - } - - private struct PreviewView: View { - let title: String - var content: () -> Content - - init(_ title: String, @ViewBuilder content: @escaping () -> Content) { - self.title = title - self.content = content - } - - var body: some View { - NavigationView { - content() - .navigationTitle("DuckDuckGo Desktop App") - .navigationBarTitleDisplayMode(.inline) - .overlay(Divider(), alignment: .top) - } - .previewDisplayName(title) - } - } -} diff --git a/DuckDuckGo/MacWaitlistViewController.swift b/DuckDuckGo/MacWaitlistViewController.swift deleted file mode 100644 index ee6f58d99b..0000000000 --- a/DuckDuckGo/MacWaitlistViewController.swift +++ /dev/null @@ -1,135 +0,0 @@ -// -// MacWaitlistViewController.swift -// DuckDuckGo -// -// Copyright © 2022 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 UIKit -import SwiftUI -import LinkPresentation -import Core -import Waitlist -import DesignResourcesKit - -final class MacWaitlistViewController: UIViewController { - - private let viewModel: WaitlistViewModel - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self.viewModel = WaitlistViewModel(waitlist: MacBrowserWaitlist.shared) - super.init(nibName: nil, bundle: nil) - self.viewModel.delegate = self - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = UserText.macBrowserTitle - addHostingControllerToViewHierarchy() - } - - private func addHostingControllerToViewHierarchy() { - let waitlistView = MacBrowserWaitlistView().environmentObject(viewModel) - let waitlistViewController = UIHostingController(rootView: waitlistView) - waitlistViewController.view.backgroundColor = UIColor(designSystemColor: .background) - - addChild(waitlistViewController) - waitlistViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(waitlistViewController.view) - waitlistViewController.didMove(toParent: self) - - NSLayoutConstraint.activate([ - waitlistViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor), - waitlistViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor), - waitlistViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - waitlistViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) - ]) - } - -} - -extension MacWaitlistViewController: WaitlistViewModelDelegate { - func waitlistViewModelDidAskToReceiveJoinedNotification(_ viewModel: WaitlistViewModel) async -> Bool { - assertionFailure("Mac Waitlist is removed") - return true - } - - func waitlistViewModelDidJoinQueueWithNotificationsAllowed(_ viewModel: WaitlistViewModel) { - assertionFailure("Mac Waitlist is removed") - } - - func waitlistViewModelDidOpenInviteCodeShareSheet(_ viewModel: WaitlistViewModel, inviteCode: String, senderFrame: CGRect) { - assertionFailure("Mac Waitlist is removed") - } - - func waitlistViewModelDidOpenDownloadURLShareSheet(_ viewModel: WaitlistViewModel, senderFrame: CGRect) { - let linkMetadata = MacWaitlistLinkMetadata() - let activityViewController = UIActivityViewController(activityItems: [linkMetadata], applicationActivities: nil) - - if UIDevice.current.userInterfaceIdiom == .pad { - activityViewController.popoverPresentationController?.sourceView = UIApplication.shared.windows.first - activityViewController.popoverPresentationController?.permittedArrowDirections = .right - activityViewController.popoverPresentationController?.sourceRect = senderFrame - } - - present(activityViewController, animated: true, completion: nil) - } - - func waitlistViewModel(_ viewModel: WaitlistViewModel, didTriggerCustomAction action: WaitlistViewModel.ViewCustomAction) { - if action == .openWindowsBrowserWaitlist { - let windowsWaitlistViewController = WindowsWaitlistViewController(nibName: nil, bundle: nil) - navigationController?.popToRootViewController(animated: true) - navigationController?.pushViewController(windowsWaitlistViewController, animated: true) - } - } -} - -private final class MacWaitlistLinkMetadata: NSObject, UIActivityItemSource { - - fileprivate let metadata: LPLinkMetadata = { - let metadata = LPLinkMetadata() - metadata.originalURL = MacBrowserWaitlist.downloadURL - metadata.url = MacBrowserWaitlist.downloadURL - metadata.title = UserText.macWaitlistShareSheetTitle - metadata.imageProvider = NSItemProvider(object: UIImage(named: "WaitlistShareSheetLogo")!) - - return metadata - }() - - func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? { - return self.metadata - } - - public func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any { - return self.metadata.originalURL as Any - } - - public func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - guard let type = activityType else { - return self.metadata.originalURL as Any - } - - switch type { - case .message, .mail: return UserText.macWaitlistShareSheetMessage - default: return self.metadata.originalURL as Any - } - } - -} diff --git a/DuckDuckGo/SettingsDebugView.swift b/DuckDuckGo/SettingsDebugView.swift index 8baacecbf5..582a33c319 100644 --- a/DuckDuckGo/SettingsDebugView.swift +++ b/DuckDuckGo/SettingsDebugView.swift @@ -21,7 +21,7 @@ import SwiftUI import UIKit struct SettingsDebugView: View { - + @EnvironmentObject var viewModel: SettingsViewModel var body: some View { diff --git a/DuckDuckGo/SettingsGeneralView.swift b/DuckDuckGo/SettingsGeneralView.swift index db6de8cc18..4a7ec0de32 100644 --- a/DuckDuckGo/SettingsGeneralView.swift +++ b/DuckDuckGo/SettingsGeneralView.swift @@ -37,6 +37,7 @@ struct SettingsGeneralView: View { NavigationLink(destination: WidgetEducationView()) { SettingsCellView(label: UserText.settingsAddWidget) } + } } diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 91cc4f888b..025139dc8c 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -55,8 +55,6 @@ class SettingsLegacyViewProvider: ObservableObject { fireproofSites, autoclearData, keyboard, - macApp, - windowsApp, netP, about, feedback, debug @@ -78,8 +76,6 @@ class SettingsLegacyViewProvider: ObservableObject { var autoclearData: UIViewController { instantiate("AutoClearSettingsViewController", fromStoryboard: "Settings") } var keyboard: UIViewController { instantiate("Keyboard", fromStoryboard: "Settings") } var feedback: UIViewController { instantiate("Feedback", fromStoryboard: "Feedback") } - var mac: UIViewController { MacWaitlistViewController(nibName: nil, bundle: nil) } - var windows: UIViewController { WindowsWaitlistViewController(nibName: nil, bundle: nil) } var about: UIViewController { AboutViewController() } @available(iOS 15.0, *) diff --git a/DuckDuckGo/SettingsMoreView.swift b/DuckDuckGo/SettingsMoreView.swift index d3a770fa19..c0af552453 100644 --- a/DuckDuckGo/SettingsMoreView.swift +++ b/DuckDuckGo/SettingsMoreView.swift @@ -34,17 +34,15 @@ struct SettingsMoreView: View { disclosureIndicator: true, isButton: true) - SettingsCellView(label: UserText.macBrowserTitle, - subtitle: UserText.macWaitlistBrowsePrivately, - action: { viewModel.presentLegacyView(.macApp) }, - disclosureIndicator: true, - isButton: true) + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .mac))) { + SettingsCellView(label: UserText.macBrowserTitle, + subtitle: UserText.macWaitlistBrowsePrivately) + } - SettingsCellView(label: UserText.windowsWaitlistTitle, - subtitle: UserText.windowsWaitlistBrowsePrivately, - action: { viewModel.presentLegacyView(.windowsApp) }, - disclosureIndicator: true, - isButton: true) + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .windows))) { + SettingsCellView(label: UserText.windowsWaitlistTitle, + subtitle: UserText.windowsWaitlistBrowsePrivately) + } #if NETWORK_PROTECTION if viewModel.state.networkProtection.enabled { diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index b74e57c6b9..d03b65ce41 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -72,11 +72,13 @@ struct SettingsSubscriptionView: View { disclosureIndicator: true, isButton: true) - /* - NavigationLink(destination: Text("Data Broker Protection"), isActive: $viewModel.shouldNavigateToDBP) { - SettingsCellView(label: UserText.settingsPProDBPTitle, subtitle: UserText.settingsPProDBPSubTitle) + + SettingsCellView(label: UserText.settingsPProDBPTitle, + subtitle: UserText.settingsPProDBPSubTitle, + action: { isShowingDBP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingDBP) { + SubscriptionPIRView() } - */ SettingsCellView(label: UserText.settingsPProITRTitle, subtitle: UserText.settingsPProITRSubTitle, @@ -94,7 +96,6 @@ struct SettingsSubscriptionView: View { } var body: some View { - if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { if viewModel.state.subscription.hasActiveSubscription { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index c379365941..f4f426b1fe 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -457,8 +457,6 @@ extension SettingsViewModel { case .fireproofSites: pushViewController(legacyViewProvider.fireproofSites) case .autoclearData: pushViewController(legacyViewProvider.autoclearData) case .keyboard: pushViewController(legacyViewProvider.keyboard) - case .windowsApp: pushViewController(legacyViewProvider.windows) - case .macApp: pushViewController(legacyViewProvider.mac) case .about: pushViewController(legacyViewProvider.about) case .debug: pushViewController(legacyViewProvider.debug) diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json new file mode 100644 index 0000000000..5c14d97759 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Information-Remover-320.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "original" + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Information-Remover-320.pdf b/DuckDuckGo/Subscription/Subscription.xcassets/PersonalInformationHero.imageset/Information-Remover-320.pdf new file mode 100644 index 0000000000000000000000000000000000000000..22fe60e1507e5b98cee0851f9180b46dc866ec94 GIT binary patch literal 245732 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

S}bv%7}JD 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 k>k|E9zREYk8_iouz|51sO)`_Ikbq5Rs2Ll7>0 + + + + + + + + + diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json new file mode 100644 index 0000000000..89531b8717 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "Platform-Windows-16.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg new file mode 100644 index 0000000000..0f6492df8a --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg @@ -0,0 +1,3 @@ + + + diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift new file mode 100644 index 0000000000..2ce1c2674b --- /dev/null +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift @@ -0,0 +1,30 @@ +// +// SubscriptionPIRViewModel.swift +// DuckDuckGo +// +// 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 Core + +#if SUBSCRIPTION +@available(iOS 15.0, *) +final class SubscriptionPIRViewModel: ObservableObject { + + var viewTitle = UserText.subscriptionTitle + +} +#endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift new file mode 100644 index 0000000000..10072d9f8a --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift @@ -0,0 +1,201 @@ +// +// SubscriptionPIRView.swift +// DuckDuckGo +// +// 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. +// + +#if SUBSCRIPTION +import SwiftUI +import Foundation +import DesignResourcesKit +import DuckUI + +@available(iOS 15.0, *) +struct SubscriptionPIRView: View { + + @Environment(\.dismiss) var dismiss + @Environment(\.colorScheme) var colorScheme + @StateObject var viewModel = SubscriptionPIRViewModel() + @State private var isShowingWindowsView = false + @State private var isShowingMacView = false + + enum Constants { + static let daxLogo = "Home" + static let daxLogoSize: CGFloat = 24.0 + static let empty = "" + static let navButtonPadding: CGFloat = 20.0 + static let lightMask: [Color] = [Color.init(0xFFFFFF, alpha: 0), Color.init(0xFFFFFF, alpha: 0)] + static let lightColors = [Color.init(0xF9F1F4), Color.init(0xF1F0FF)] + static let darkMask = [Color.init(0x2F2F2F, alpha: 0), Color.init(0x2F2F2F, alpha: 1)] + static let darkColors = [Color.init(0x3C184E), Color.init(0x3F1844), Color.init(0x3B1A36)] + static let titleMaxWidth = 200.0 + static let headerPadding = 5.0 + static let generalSpacing = 20.0 + static let cornerRadius = 10.0 + static let windowsIcon = "Platform-Windows-16" + static let macOSIcon = "Platform-Apple-16" + } + + var body: some View { + NavigationView { + ZStack { + gradientBackground + ScrollView { + VStack { + header + .padding(.top, Constants.headerPadding) + baseView + .frame(maxWidth: 600) + } + } + + } + .edgesIgnoringSafeArea(.all) + } + } + + private var header: some View { + GeometryReader { geometry in + HStack { + Spacer().frame(width: geometry.size.width / 3) + HStack(alignment: .center) { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(viewModel.viewTitle).daxBodyRegular() + } + .frame(width: geometry.size.width / 3, alignment: .center) + dismissButton + .frame(width: geometry.size.width / 3, alignment: .trailing) + } + } + } + + private var gradientBackground: some View { + ZStack { + LinearGradient(colors: colorScheme == . dark ? Constants.darkColors : Constants.lightColors, + startPoint: .top, + endPoint: .bottom) + LinearGradient(colors: colorScheme == . dark ? Constants.darkMask : Constants.lightMask, + startPoint: .top, + endPoint: .bottom) + } + } + + private var baseView: some View { + VStack(alignment: .center, spacing: Constants.generalSpacing) { + Image("PersonalInformationHero") + .aspectRatio(contentMode: .fill) + .padding(.top, Constants.generalSpacing) + VStack { + Text(UserText.subscriptionPIRHeroText) + .daxTitle2() + .multilineTextAlignment(.center) + .padding(.horizontal, Constants.generalSpacing*2) + .foregroundColor(Color(designSystemColor: .textPrimary)) + .padding(.bottom, Constants.generalSpacing) + attributedDescription + .padding(.horizontal, Constants.generalSpacing) + .multilineTextAlignment(.center) + .padding(.horizontal, Constants.generalSpacing) + } + Spacer() + Spacer() + VStack { + macOSButton + windowsButton + } + .padding(.bottom, Constants.generalSpacing*2) + + } + } + + private var attributedDescription: some View { + let baseStringFormat = UserText.subscriptionPIRHeroDetail + let insertString1 = UserText.subscriptionPIRHeroDesktopMenuLocation + let insertString2 = UserText.subscriptionPIRHeroDesktopMenuItem + + let highlightFont = Font(uiFont: .daxBodyBold()) + + let fullString = String(format: baseStringFormat, insertString1, insertString2) + var attributedString = AttributedString(fullString) + attributedString.font = .daxBodyRegular() + + if let range1 = attributedString.range(of: insertString1) { + attributedString[range1].font = highlightFont + } + + if let range2 = attributedString.range(of: insertString2) { + attributedString[range2].font = highlightFont} + + return Text(attributedString) + } + + @ViewBuilder + private var windowsButton: some View { + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .windows)), + isActive: $isShowingWindowsView) { + HStack { + Image(Constants.windowsIcon) + Text(UserText.subscriptionPIRWindows) + } + .frame(maxWidth: .infinity) + .padding() + .foregroundColor(Color(designSystemColor: .accent)) + .daxButton() + .overlay( + RoundedRectangle(cornerRadius: Constants.cornerRadius) + .stroke(Color(designSystemColor: .accent), lineWidth: 1) + + ) + .padding(.horizontal, Constants.generalSpacing) + } + } + + @ViewBuilder + private var macOSButton: some View { + NavigationLink(destination: DesktopDownloadView(viewModel: .init(platform: .mac)), + isActive: $isShowingMacView) { + HStack { + Image(Constants.macOSIcon) + Text(UserText.subscriptionPIRMacOS) + } + .frame(maxWidth: .infinity) + .padding() + .foregroundColor(Color(designSystemColor: .accent)) + .daxButton() + .overlay( + RoundedRectangle(cornerRadius: Constants.cornerRadius) + .stroke(Color(designSystemColor: .accent), lineWidth: 1) + + ) + .padding(.horizontal, Constants.generalSpacing) + } + } + + @ViewBuilder + private var dismissButton: some View { + Button(action: { dismiss() }, label: { Text(UserText.subscriptionCloseButton) }) + .padding(Constants.navButtonPadding) + .contentShape(Rectangle()) + .daxBodyRegular() + .tint(Color(designSystemColor: .textPrimary)) + } + +} + +#endif diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 9c24c77556..3578eb72cf 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -445,7 +445,6 @@ public struct UserText { public static let macWaitlistSummary = NSLocalizedString("mac-browser.waitlist.summary", value: "DuckDuckGo for Mac has the speed you need, the browsing features you expect, and comes packed with our best-in-class privacy essentials.", comment: "Summary text for the macOS browser waitlist") public static let macWaitlistTryDuckDuckGoForMac = NSLocalizedString("mac-waitlist.join-waitlist-screen.try-duckduckgo-for-mac", value: "Get DuckDuckGo for Mac!", comment: "Title for the Join Waitlist screen") public static let macWaitlistOnYourMacGoTo = NSLocalizedString("mac-waitlist.join-waitlist-screen.on-your-mac-go-to", value: "On your Mac, go to:", comment: "Description text above the Share Link button") - public static let macWaitlistWindowsComingSoon = NSLocalizedString("mac-waitlist.join-waitlist-screen.windows", value: "Windows coming soon!", comment: "Disclaimer for the Join Waitlist screen") public static let macWaitlistWindows = NSLocalizedString("mac-waitlist.join-waitlist-screen.windows-waitlist", value: "Looking for the Windows version?", comment: "Title for the macOS waitlist button redirecting to Windows waitlist") public static let macWaitlistCopy = NSLocalizedString("mac-waitlist.copy", value: "Copy", comment: "Title for the copy action") public static let macWaitlistShareLink = NSLocalizedString("mac-waitlist.join-waitlist-screen.share-link", value: "Share Link", comment: "Title for the Share Link button") @@ -456,41 +455,9 @@ public struct UserText { public static let windowsWaitlistSummary = NSLocalizedString("windows-waitlist.summary", value: "DuckDuckGo for Windows has what you need to browse with more privacy — private search, tracker blocking, forced encryption, and cookie pop-up blocking, plus more best-in-class protections on the way.", comment: "Summary text for the Windows browser waitlist") public static let windowsWaitlistOnYourComputerGoTo = NSLocalizedString("mac-waitlist.join-waitlist-screen.on-your-computer-go-to", value: "On your Windows computer, go to:", comment: "Description text above the Share Link button") public static let windowsWaitlistTryDuckDuckGoForWindowsDownload = NSLocalizedString("windows-waitlist.waitlist-download-screen.try-duckduckgo-for-windows", value: "Get DuckDuckGo for Windows!", comment: "Title for the Windows browser download link page") - public static let windowsWaitlistTryDuckDuckGoForWindows = NSLocalizedString("windows-waitlist.join-waitlist-screen.try-duckduckgo-for-windows", value: "Get early access to try DuckDuckGo for Windows!", comment: "Title for the Join Windows Waitlist screen") - public static let windowsWaitlistMac = NSLocalizedString("windows-waitlist.join-waitlist-screen.mac-waitlist", value: "Looking for the Mac version?", comment: "Title for the Windows waitlist button redirecting to Mac waitlist") + public static let windowsWaitlistMac = NSLocalizedString("windows-waitlist.join-waitlist-screen.mac-waitlist", value: "Looking for the Mac version?", comment: "Title for the Windows waitlist button redirecting to Mac waitlist") public static let windowsWaitlistBrowsePrivately = NSLocalizedString("windows-waitlist.settings.browse-privately", value: "Browse privately with our app for Windows", comment: "Title for the settings subtitle") - public static let windowsWaitlistJoinedWithNotifications = NSLocalizedString("windows-waitlist.joined.notifications-enabled", - value: "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download.", - comment: "Label text for the Joined Waitlist state with notifications enabled") - public static let windowsWaitlistJoinedWithoutNotifications = NSLocalizedString("windows-waitlist.joined.notifications-declined", - value: "Your invite to try DuckDuckGo for Windows will arrive here. Check back soon, or we can send you a notification when it’s your turn.", - comment: "Label text for the Joined Waitlist state with notifications declined") - public static let windowsWaitlistNotifyMeConfirmationMessage = NSLocalizedString("windows-waitlist.joined.no-notification.get-notification-confirmation-message", value: "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download. ", comment: "Message for the alert to confirm enabling notifications") - public static let windowsWaitlistInviteScreenSubtitle = NSLocalizedString("windows-waitlist.invite-screen.subtitle", value: "Ready to use DuckDuckGo on Windows?", comment: "Subtitle for the Windows Waitlist Invite screen") - public static let windowsWaitlistInviteScreenStep1Description = NSLocalizedString("windows-waitlist.invite-screen.step-1.description", value: "Visit this URL on your Windows device to download:", comment: "Description on the invite screen") - public static let windowsWaitlistInviteScreenStep2Description = NSLocalizedString("windows-waitlist.invite-screen.step-2.description", value: "Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code.", comment: "Description on the invite screen") - public static let windowsWaitlistAvailableNotificationTitle = NSLocalizedString("windows-waitlist.available.notification.title", value: "Try DuckDuckGo for Windows!", comment: "Title for the Windows waitlist notification") - public static func windowsWaitlistShareSheetMessage(code: String) -> String { - let localized = NSLocalizedString("windows-waitlist.share-sheet.invite-code-message", value: """ - You’re invited! - - Ready to use DuckDuckGo on Windows? - - Step 1 - Visit this URL on your Windows device to download: - https://duckduckgo.com/windows - - Step 2 - Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code. - - Invite code: %@ - """, comment: "Message used when sharing to iMessage. Parameter is an eight digit invite code.") - - return localized.format(arguments: code) - } - - // MARK: App Tracking Protection public static let appTPOnboardingTitle1 = NSLocalizedString("appTP.onboarding.title1", value: "One easy step for better app privacy!", comment: "Title for first AppTP onboarding page") @@ -1099,5 +1066,13 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionRestoreSuccessfulTitle = NSLocalizedString("subscription.restore.success.alert.title", value: "You’re all set.", comment: "Alert title for restored purchase") public static let subscriptionRestoreSuccessfulMessage = NSLocalizedString("subscription.restore.success.alert.message", value: "Your purchases have been restored.", comment: "Alert message for restored purchase") public static let subscriptionRestoreSuccessfulButton = NSLocalizedString("subscription.restore.success.alert.button", value: "OK", comment: "Alert button text for restored purchase alert") - + + // PIR: + public static let subscriptionPIRHeroText = NSLocalizedString("subscription.pir.hero", value: "Activate Privacy Pro on desktop to set up Personal Information Removal", comment: "Hero Text for Personal information removal") + public static let subscriptionPIRHeroDetail = NSLocalizedString("subscription.pir.heroText", value: "In the DuckDuckGo browser for desktop, go to %@ and click %@ to get started.", comment: "Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. ") + public static let subscriptionPIRHeroDesktopMenuLocation = NSLocalizedString("subscription.pir.heroTextLocation", value: "Settings > Privacy Pro", comment: "Settings references a menu in the Desktop app, Privacy Pro, references our product name") + public static let subscriptionPIRHeroDesktopMenuItem = NSLocalizedString("subscription.pir.heroTextMenyEntry", value: "I have a subscription", comment: "Menu item for enabling Personal Information Removal on Desktop") + public static let subscriptionPIRWindows = NSLocalizedString("subscription.pir.windows", value: "Windows", comment: "Text for the 'Windows' button") + public static let subscriptionPIRMacOS = NSLocalizedString("subscription.pir.macos", value: "macOS", comment: "Text for the 'macOS' button") + } diff --git a/DuckDuckGo/WindowsBrowserWaitlist.swift b/DuckDuckGo/WindowsBrowserWaitlist.swift deleted file mode 100644 index ad628591e1..0000000000 --- a/DuckDuckGo/WindowsBrowserWaitlist.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// WindowsBrowserWaitlist.swift -// DuckDuckGo -// -// Copyright © 2023 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 Combine -import Core -import Waitlist - -final class WindowsBrowserWaitlist: Waitlist { - static let identifier: String = "windows" - static let apiProductName: String = "windowsbrowser" - static let downloadURL: URL = URL.windows - - static let shared: WindowsBrowserWaitlist = .init() - - static let backgroundTaskName = "Windows Browser Waitlist Status Task" - static let backgroundRefreshTaskIdentifier = "com.duckduckgo.app.windowsBrowserWaitlistStatus" - static let notificationIdentifier = "com.duckduckgo.ios.windows-browser.invite-code-available" - static let inviteAvailableNotificationTitle = UserText.windowsWaitlistAvailableNotificationTitle - static let inviteAvailableNotificationBody = UserText.waitlistAvailableNotificationBody - - var isAvailable: Bool { - isFeatureEnabled - } - - var isWaitlistRemoved: Bool = false - let waitlistStorage: WaitlistStorage - let waitlistRequest: WaitlistRequest - - init(store: WaitlistStorage, request: WaitlistRequest, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager) { - self.waitlistStorage = store - self.waitlistRequest = request - - isFeatureEnabled = privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .windowsWaitlist) - isWaitlistRemoved = privacyConfigurationManager.privacyConfig.isEnabled(featureKey: .windowsDownloadLink) - - isFeatureEnabledCancellable = privacyConfigurationManager.updatesPublisher - .map { [weak privacyConfigurationManager] in - privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .windowsWaitlist) == true - } - .receive(on: DispatchQueue.main) - .assign(to: \.isFeatureEnabled, onWeaklyHeld: self) - - isWaitlistRemovedCancellable = privacyConfigurationManager.updatesPublisher - .map { [weak privacyConfigurationManager] in - privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .windowsDownloadLink) == true - } - .receive(on: DispatchQueue.main) - .assign(to: \.isWaitlistRemoved, onWeaklyHeld: self) - } - - convenience init(store: WaitlistStorage, request: WaitlistRequest) { - self.init(store: store, request: request, privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager) - } - - var settingsSubtitle: String { - if isWaitlistRemoved { - return UserText.windowsWaitlistBrowsePrivately - } - - if waitlistStorage.isInvited { - return UserText.waitlistDownloadAvailable - } - - if waitlistStorage.isOnWaitlist { - return UserText.waitlistOnTheList - } - - return UserText.windowsWaitlistBrowsePrivately - } - - // MARK: - - - private var isFeatureEnabled: Bool = false - private var modeCancellable: AnyCancellable? - private var isFeatureEnabledCancellable: AnyCancellable? - private var isWaitlistRemovedCancellable: AnyCancellable? -} - -extension WaitlistViewModel.ViewCustomAction { - static var openMacBrowserWaitlist = WaitlistViewModel.ViewCustomAction(identifier: "openMacBrowserWaitlist") -} diff --git a/DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift b/DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift deleted file mode 100644 index ce1732786e..0000000000 --- a/DuckDuckGo/WindowsBrowserWaitlistDebugViewController.swift +++ /dev/null @@ -1,190 +0,0 @@ -// -// WindowsBrowserWaitlistDebugViewController.swift -// DuckDuckGo -// -// Copyright © 2022 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 UIKit -import Core -import BackgroundTasks -import Waitlist - -final class WindowsBrowserWaitlistDebugViewController: UITableViewController { - - enum Sections: Int, CaseIterable { - - case waitlistInformation - case debuggingActions - - } - - private let waitlistInformationTitles = [ - WaitlistInformationRows.waitlistTimestamp: "Timestamp", - WaitlistInformationRows.waitlistToken: "Token", - WaitlistInformationRows.waitlistInviteCode: "Invite Code", - WaitlistInformationRows.backgroundTask: "Earliest Refresh Date" - ] - - enum WaitlistInformationRows: Int, CaseIterable { - - case waitlistTimestamp - case waitlistToken - case waitlistInviteCode - case backgroundTask - - } - - private let debuggingActionTitles = [ - DebuggingActionRows.scheduleWaitlistNotification: "Fire Waitlist Notification in 3 seconds", - DebuggingActionRows.setMockInviteCode: "Set Mock Invite Code", - DebuggingActionRows.deleteInviteCode: "Delete Invite Code" - ] - - enum DebuggingActionRows: Int, CaseIterable { - - case scheduleWaitlistNotification - case setMockInviteCode - case deleteInviteCode - - } - - private let storage = WaitlistKeychainStore(waitlistIdentifier: WindowsBrowserWaitlist.identifier) - - private var backgroundTaskExecutionDate: String? - - override func viewDidLoad() { - super.viewDidLoad() - - let clearDataItem = UIBarButtonItem(image: UIImage(systemName: "trash")!, - style: .done, - target: self, - action: #selector(presentClearDataPrompt(_:))) - clearDataItem.tintColor = .systemRed - navigationItem.rightBarButtonItem = clearDataItem - - BGTaskScheduler.shared.getPendingTaskRequests { tasks in - if let task = tasks.first(where: { $0.identifier == WindowsBrowserWaitlist.backgroundRefreshTaskIdentifier }) { - let formatter = DateFormatter() - formatter.dateStyle = .short - formatter.timeStyle = .medium - - self.backgroundTaskExecutionDate = formatter.string(from: task.earliestBeginDate!) - - DispatchQueue.main.async { - self.tableView.reloadData() - } - } - } - } - - override func numberOfSections(in tableView: UITableView) -> Int { - return Sections.allCases.count - } - - override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - switch Sections(rawValue: section)! { - case .waitlistInformation: return WaitlistInformationRows.allCases.count - case .debuggingActions: return DebuggingActionRows.allCases.count - } - } - - override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - let section = Sections(rawValue: indexPath.section)! - - switch section { - case .waitlistInformation: - let cell = tableView.dequeueReusableCell(withIdentifier: "DetailCell", for: indexPath) - let row = WaitlistInformationRows(rawValue: indexPath.row)! - cell.textLabel?.text = waitlistInformationTitles[row] - - switch row { - case .waitlistTimestamp: - if let timestamp = storage.getWaitlistTimestamp() { - cell.detailTextLabel?.text = String(timestamp) - } else { - cell.detailTextLabel?.text = "None" - } - - case .waitlistToken: - cell.detailTextLabel?.text = storage.getWaitlistToken() ?? "None" - - case .waitlistInviteCode: - cell.detailTextLabel?.text = storage.getWaitlistInviteCode() ?? "None" - - case .backgroundTask: - cell.detailTextLabel?.text = backgroundTaskExecutionDate ?? "None" - } - - return cell - - case .debuggingActions: - let cell = tableView.dequeueReusableCell(withIdentifier: "ActionCell", for: indexPath) - let row = DebuggingActionRows(rawValue: indexPath.row)! - cell.textLabel?.text = debuggingActionTitles[row] - - return cell - } - - } - - override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - let section = Sections(rawValue: indexPath.section)! - - switch section { - case .waitlistInformation: break - case .debuggingActions: - let row = DebuggingActionRows(rawValue: indexPath.row)! - - switch row { - case .scheduleWaitlistNotification: - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 3) { - self.storage.store(inviteCode: "ABCD1234") - WindowsBrowserWaitlist.shared.sendInviteCodeAvailableNotification() - } - case .setMockInviteCode: - storage.store(inviteCode: "ABCD1234") - case .deleteInviteCode: - storage.delete(field: .inviteCode) - tableView.reloadData() - } - } - - tableView.deselectRow(at: indexPath, animated: true) - tableView.reloadData() - } - - @objc - private func presentClearDataPrompt(_ sender: AnyObject) { - let alert = UIAlertController(title: "Clear Waitlist Data?", message: nil, preferredStyle: .actionSheet) - - if UIDevice.current.userInterfaceIdiom == .pad { - alert.popoverPresentationController?.barButtonItem = (sender as? UIBarButtonItem) - } - - alert.addAction(UIAlertAction(title: "Clear Data", style: .destructive, handler: { _ in - self.clearDataAndReload() - })) - - alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) - - present(alert, animated: true) - } - - private func clearDataAndReload() { - storage.deleteWaitlistState() - tableView.reloadData() - } -} diff --git a/DuckDuckGo/WindowsBrowserWaitlistView.swift b/DuckDuckGo/WindowsBrowserWaitlistView.swift deleted file mode 100644 index f6a0796f21..0000000000 --- a/DuckDuckGo/WindowsBrowserWaitlistView.swift +++ /dev/null @@ -1,348 +0,0 @@ -// -// WindowsBrowserWaitlistView.swift -// DuckDuckGo -// -// Copyright © 2023 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 Core -import Waitlist -import DesignResourcesKit - -struct WindowsBrowserWaitlistView: View { - - @EnvironmentObject var viewModel: WaitlistViewModel - - var body: some View { - switch viewModel.viewState { - case .notJoinedQueue: - WindowsBrowserWaitlistSignUpView(requestInFlight: false) { action in - Task { await viewModel.perform(action: action) } - } - case .joiningQueue: - WindowsBrowserWaitlistSignUpView(requestInFlight: true) { action in - Task { await viewModel.perform(action: action) } - } - case .joinedQueue(let state): - WindowsBrowserWaitlistJoinedWaitlistView(notificationState: state) { action in - Task { await viewModel.perform(action: action) } - } - case .invited(let inviteCode): - WindowsBrowserWaitlistInvitedView(inviteCode: inviteCode) { action in - Task { await viewModel.perform(action: action) } - } - case .waitlistRemoved: - WaitlistDownloadBrowserContentView(platform: .windows) { action in - Task { await viewModel.perform(action: action) } - } - } - } -} - -struct WindowsBrowserWaitlistSignUpView: View { - - let requestInFlight: Bool - - let action: WaitlistViewActionHandler - - var body: some View { - GeometryReader { proxy in - ScrollView { - VStack(alignment: .center, spacing: 8) { - HeaderView(imageName: "WindowsWaitlistJoinWaitlist", title: UserText.windowsWaitlistTryDuckDuckGoForWindows) - - Text(UserText.windowsWaitlistSummary) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .multilineTextAlignment(.center) - .lineSpacing(6) - - Button(UserText.waitlistJoin, action: { action(.joinQueue) }) - .buttonStyle(RoundedButtonStyle(enabled: !requestInFlight)) - .padding(.top, 24) - - if requestInFlight { - HStack { - Text(UserText.waitlistJoining) - .daxSubheadRegular() - .foregroundColor(.waitlistTextSecondary) - - ActivityIndicator(style: .medium) - } - .padding(.top, 14) - } - - Spacer(minLength: 24) - - Button( - action: { - action(.custom(.openMacBrowserWaitlist)) - }, label: { - Text(UserText.windowsWaitlistMac) - .daxHeadline() - .foregroundColor(.waitlistBlue) - .multilineTextAlignment(.center) - .lineSpacing(5) - } - ) - .padding(.bottom, 12) - .fixedSize(horizontal: false, vertical: true) - - Text(UserText.waitlistPrivacyDisclaimer) - .daxFootnoteRegular() - .foregroundColor(.waitlistTextSecondary) - .multilineTextAlignment(.center) - .lineSpacing(5) - .padding(.bottom, 12) - .fixedSize(horizontal: false, vertical: true) - } - .padding([.leading, .trailing], 24) - .frame(minHeight: proxy.size.height) - } - } - } - -} - -// MARK: - Joined Waitlist Views - -struct WindowsBrowserWaitlistJoinedWaitlistView: View { - - let notificationState: WaitlistViewModel.NotificationPermissionState - - let action: (WaitlistViewModel.ViewAction) -> Void - - var body: some View { - VStack(spacing: 16) { - HeaderView(imageName: "WaitlistJoined", title: UserText.waitlistOnTheList) - - switch notificationState { - case .notificationAllowed: - Text(UserText.windowsWaitlistJoinedWithNotifications) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - default: - Text(UserText.windowsWaitlistJoinedWithoutNotifications) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - if notificationState == .notificationsDisabled { - AllowNotificationsView(action: action) - .padding(.top, 4) - } else { - Button(UserText.waitlistNotifyMe) { - action(.requestNotificationPermission) - } - .buttonStyle(RoundedButtonStyle(enabled: true)) - .padding(.top, 32) - } - } - - Spacer() - } - .padding([.leading, .trailing], 24) - .multilineTextAlignment(.center) - } - -} - -private struct AllowNotificationsView: View { - - let action: (WaitlistViewModel.ViewAction) -> Void - - var body: some View { - - VStack(spacing: 20) { - - Text(UserText.waitlistNotificationDisabled) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .fixMultilineScrollableText() - .lineSpacing(5) - - Button(UserText.waitlistAllowNotifications) { - action(.openNotificationSettings) - } - .buttonStyle(RoundedButtonStyle(enabled: true)) - - } - .padding(24) - .background(Color.waitlistNotificationBackground) - .cornerRadius(8) - .shadow(color: .black.opacity(0.05), radius: 12, x: 0, y: 4) - - } - -} - -// MARK: - Invite Available Views - -private struct ShareButtonFramePreferenceKey: PreferenceKey { - static var defaultValue: CGRect = .zero - static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} -} - -struct WindowsBrowserWaitlistInvitedView: View { - - let inviteCode: String - let action: (WaitlistViewModel.ViewAction) -> Void - - @State private var shareButtonFrame: CGRect = .zero - - var body: some View { - GeometryReader { proxy in - ScrollView { - VStack(alignment: .center, spacing: 0) { - HeaderView(imageName: "WaitlistInvited", title: UserText.waitlistYoureInvited) - - Text(UserText.windowsWaitlistInviteScreenSubtitle) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .padding(.top, 16) - .lineSpacing(6) - .fixedSize(horizontal: false, vertical: true) - - Text(UserText.waitlistInviteScreenStepTitle(step: 1)) - .daxHeadline() - .foregroundColor(.waitlistTextSecondary) - .padding(.top, 28) - .padding(.bottom, 8) - - Text(UserText.windowsWaitlistInviteScreenStep1Description) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - Text(URL.windows.absoluteString.dropping(prefix: "https://")) - .daxHeadline() - .foregroundColor(.waitlistBlue) - .menuController(UserText.waitlistCopy) { - action(.copyDownloadURLToPasteboard) - } - .scaledToFit() - - Text(UserText.waitlistInviteScreenStepTitle(step: 2)) - .daxHeadline() - .foregroundColor(.waitlistTextSecondary) - .padding(.top, 22) - .padding(.bottom, 8) - - Text(UserText.windowsWaitlistInviteScreenStep2Description) - .daxBodyRegular() - .foregroundColor(.waitlistTextSecondary) - .lineSpacing(6) - - InviteCodeView(title: UserText.waitlistInviteCode, inviteCode: inviteCode) - .menuController(UserText.waitlistCopy) { - action(.copyInviteCodeToPasteboard) - } - .fixedSize() - .padding(.top, 28) - - Spacer(minLength: 24) - - shareButton - .padding(.bottom, 26) - - } - .frame(maxWidth: .infinity, minHeight: proxy.size.height) - .padding([.leading, .trailing], 18) - .multilineTextAlignment(.center) - } - } - } - - var shareButton: some View { - - Button(action: { - action(.openShareSheet(shareButtonFrame)) - }, label: { - Image("Share") - .foregroundColor(.waitlistTextSecondary) - }) - .frame(width: 44, height: 44) - .background( - GeometryReader { proxy in - Color.clear - .preference(key: ShareButtonFramePreferenceKey.self, value: proxy.frame(in: .global)) - } - ) - .onPreferenceChange(ShareButtonFramePreferenceKey.self) { newFrame in - if UIDevice.current.userInterfaceIdiom == .pad { - self.shareButtonFrame = newFrame - } - } - - } - -} - -// MARK: - Previews - -private struct WindowsBrowserWaitlistView_Previews: PreviewProvider { - - static var previews: some View { - Group { - PreviewView("Sign Up") { - WindowsBrowserWaitlistSignUpView(requestInFlight: false) { _ in } - } - - PreviewView("Sign Up (API Request In Progress)") { - WindowsBrowserWaitlistSignUpView(requestInFlight: true) { _ in } - } - - PreviewView("Joined Waitlist (Notifications Allowed)") { - WindowsBrowserWaitlistJoinedWaitlistView(notificationState: .notificationAllowed) { _ in } - } - - PreviewView("Joined Waitlist (Notifications Not Allowed)") { - WindowsBrowserWaitlistJoinedWaitlistView(notificationState: .notificationsDisabled) { _ in } - } - - PreviewView("Invite Screen With Code") { - WindowsBrowserWaitlistInvitedView(inviteCode: "T3STC0DE") { _ in } - } - - if #available(iOS 15.0, *) { - WindowsBrowserWaitlistInvitedView(inviteCode: "T3STC0DE") { _ in } - .previewInterfaceOrientation(.landscapeLeft) - } - } - } - - private struct PreviewView: View { - let title: String - var content: () -> Content - - init(_ title: String, @ViewBuilder content: @escaping () -> Content) { - self.title = title - self.content = content - } - - var body: some View { - NavigationView { - content() - .navigationTitle("DuckDuckGo Desktop App") - .navigationBarTitleDisplayMode(.inline) - .overlay(Divider(), alignment: .top) - } - .previewDisplayName(title) - } - } -} diff --git a/DuckDuckGo/WindowsWaitlistViewController.swift b/DuckDuckGo/WindowsWaitlistViewController.swift deleted file mode 100644 index 9ba9d561e4..0000000000 --- a/DuckDuckGo/WindowsWaitlistViewController.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// WindowsWaitlistViewController.swift -// DuckDuckGo -// -// Copyright © 2022 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 UIKit -import SwiftUI -import LinkPresentation -import Core -import Waitlist - -final class WindowsWaitlistViewController: UIViewController { - - private let viewModel: WaitlistViewModel - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - self.viewModel = WaitlistViewModel(waitlist: WindowsBrowserWaitlist.shared) - super.init(nibName: nil, bundle: nil) - self.viewModel.delegate = self - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - title = UserText.windowsWaitlistTitle - - addHostingControllerToViewHierarchy() - - NotificationCenter.default.addObserver(self, - selector: #selector(updateViewState), - name: UIApplication.didBecomeActiveNotification, - object: nil) - - NotificationCenter.default.addObserver(self, - selector: #selector(updateViewState), - name: WaitlistKeychainStore.inviteCodeDidChangeNotification, - object: WindowsBrowserWaitlist.identifier) - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - Task { - await self.viewModel.updateViewState() - } - } - - @objc - private func updateViewState() { - Task { - await self.viewModel.updateViewState() - } - } - - private func addHostingControllerToViewHierarchy() { - let waitlistView = WindowsBrowserWaitlistView().environmentObject(viewModel) - let waitlistViewController = UIHostingController(rootView: waitlistView) - waitlistViewController.view.backgroundColor = UIColor(designSystemColor: .background) - - addChild(waitlistViewController) - waitlistViewController.view.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(waitlistViewController.view) - waitlistViewController.didMove(toParent: self) - - NSLayoutConstraint.activate([ - waitlistViewController.view.widthAnchor.constraint(equalTo: view.widthAnchor), - waitlistViewController.view.heightAnchor.constraint(equalTo: view.heightAnchor), - waitlistViewController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - waitlistViewController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor) - ]) - } - -} - -extension WindowsWaitlistViewController: WaitlistViewModelDelegate { - - func waitlistViewModelDidAskToReceiveJoinedNotification(_ viewModel: WaitlistViewModel) async -> Bool { - return await withCheckedContinuation { continuation in - let alertController = UIAlertController(title: UserText.waitlistNotifyMeConfirmationTitle, - message: UserText.windowsWaitlistNotifyMeConfirmationMessage, - preferredStyle: .alert) - alertController.overrideUserInterfaceStyle() - - alertController.addAction(title: UserText.waitlistNoThanks) { - continuation.resume(returning: false) - } - let notifyMeAction = UIAlertAction(title: UserText.waitlistNotifyMe, style: .default) { _ in - continuation.resume(returning: true) - } - - alertController.addAction(notifyMeAction) - alertController.preferredAction = notifyMeAction - - present(alertController, animated: true) - } - } - - func waitlistViewModelDidJoinQueueWithNotificationsAllowed(_ viewModel: WaitlistViewModel) { - WindowsBrowserWaitlist.shared.scheduleBackgroundRefreshTask() - } - - func waitlistViewModelDidOpenInviteCodeShareSheet(_ viewModel: WaitlistViewModel, inviteCode: String, senderFrame: CGRect) { - openShareSheetWithMetadata(WindowsWaitlistLinkMetadata(pageType: .waitlist), senderFrame: senderFrame) - } - - func waitlistViewModelDidOpenDownloadURLShareSheet(_ viewModel: WaitlistViewModel, senderFrame: CGRect) { - openShareSheetWithMetadata(WindowsWaitlistLinkMetadata(pageType: .download), senderFrame: senderFrame) - } - - private func openShareSheetWithMetadata(_ linkMetadata: WindowsWaitlistLinkMetadata, senderFrame: CGRect) { - let activityViewController = UIActivityViewController(activityItems: [linkMetadata], applicationActivities: nil) - - if UIDevice.current.userInterfaceIdiom == .pad { - activityViewController.popoverPresentationController?.sourceView = UIApplication.shared.windows.first - activityViewController.popoverPresentationController?.permittedArrowDirections = .right - activityViewController.popoverPresentationController?.sourceRect = senderFrame - } - - present(activityViewController, animated: true, completion: nil) - } - - func waitlistViewModel(_ viewModel: WaitlistViewModel, didTriggerCustomAction action: WaitlistViewModel.ViewCustomAction) { - if action == .openMacBrowserWaitlist { - let macWaitlistViewController = MacWaitlistViewController(nibName: nil, bundle: nil) - navigationController?.popToRootViewController(animated: true) - navigationController?.pushViewController(macWaitlistViewController, animated: true) - } - } -} - -private final class WindowsWaitlistLinkMetadata: NSObject, UIActivityItemSource { - - fileprivate let metadata: LPLinkMetadata = { - let metadata = LPLinkMetadata() - metadata.originalURL = WindowsBrowserWaitlist.downloadURL - metadata.url = WindowsBrowserWaitlist.downloadURL - metadata.title = UserText.waitlistShareSheetTitle - metadata.imageProvider = NSItemProvider(object: UIImage(named: "WaitlistShareSheetLogo")!) - - return metadata - }() - - private let inviteCode: String? - private let pageType: WindowsWaitlistPageType - - init(inviteCode: String? = nil, pageType: WindowsWaitlistPageType) { - self.inviteCode = inviteCode - self.pageType = pageType - } - - func activityViewControllerLinkMetadata(_: UIActivityViewController) -> LPLinkMetadata? { - return self.metadata - } - - public func activityViewControllerPlaceholderItem(_: UIActivityViewController) -> Any { - return self.metadata.originalURL as Any - } - - public func activityViewController(_: UIActivityViewController, itemForActivityType activityType: UIActivity.ActivityType?) -> Any? { - guard let type = activityType else { - return self.metadata.originalURL as Any - } - - switch type { - case .message, .mail: - return pageType == .waitlist ? - UserText.windowsWaitlistShareSheetMessage(code: inviteCode!) : - UserText.windowsWaitlistDownloadLinkShareSheetMessage - default: - return self.metadata.originalURL as Any - } - } - - enum WindowsWaitlistPageType { - case waitlist - case download - } - -} diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 69faa3d347..db79bdb37c 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1372,9 +1372,6 @@ /* Title for the Join Waitlist screen */ "mac-waitlist.join-waitlist-screen.try-duckduckgo-for-mac" = "Get DuckDuckGo for Mac!"; -/* Disclaimer for the Join Waitlist screen */ -"mac-waitlist.join-waitlist-screen.windows" = "Windows coming soon!"; - /* Title for the macOS waitlist button redirecting to Windows waitlist */ "mac-waitlist.join-waitlist-screen.windows-waitlist" = "Looking for the Windows version?"; @@ -2022,6 +2019,24 @@ But if you *do* want a peek under the hood, you can find more information about /* View plans button text */ "subscription.notFound.view.plans" = "View Plans"; +/* Hero Text for Personal information removal */ +"subscription.pir.hero" = "Activate Privacy Pro on desktop to set up Personal Information Removal"; + +/* Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. */ +"subscription.pir.heroText" = "In the DuckDuckGo browser for desktop, go to %1$@ and click %2$@ to get started."; + +/* Settings references a menu in the Desktop app, Privacy Pro, references our product name */ +"subscription.pir.heroTextLocation" = "Settings > Privacy Pro"; + +/* Menu item for enabling Personal Information Removal on Desktop */ +"subscription.pir.heroTextMenyEntry" = "I have a subscription"; + +/* Text for the 'macOS' button */ +"subscription.pir.macos" = "macOS"; + +/* Text for the 'Windows' button */ +"subscription.pir.windows" = "Windows"; + /* Progress view title when completing the purchase */ "subscription.progress.view.completing.purchase" = "Completing purchase..."; @@ -2331,50 +2346,12 @@ But if you *do* want a peek under the hood, you can find more information about /* Alert title explaining the message is shown by a website */ "webJSAlert.website-message.format" = "A message from %@:"; -/* Title for the Windows waitlist notification */ -"windows-waitlist.available.notification.title" = "Try DuckDuckGo for Windows!"; - -/* Description on the invite screen */ -"windows-waitlist.invite-screen.step-1.description" = "Visit this URL on your Windows device to download:"; - -/* Description on the invite screen */ -"windows-waitlist.invite-screen.step-2.description" = "Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code."; - -/* Subtitle for the Windows Waitlist Invite screen */ -"windows-waitlist.invite-screen.subtitle" = "Ready to use DuckDuckGo on Windows?"; - /* Title for the Windows waitlist button redirecting to Mac waitlist */ "windows-waitlist.join-waitlist-screen.mac-waitlist" = "Looking for the Mac version?"; -/* Title for the Join Windows Waitlist screen */ -"windows-waitlist.join-waitlist-screen.try-duckduckgo-for-windows" = "Get early access to try DuckDuckGo for Windows!"; - -/* Message for the alert to confirm enabling notifications */ -"windows-waitlist.joined.no-notification.get-notification-confirmation-message" = "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download. "; - -/* Label text for the Joined Waitlist state with notifications declined */ -"windows-waitlist.joined.notifications-declined" = "Your invite to try DuckDuckGo for Windows will arrive here. Check back soon, or we can send you a notification when it’s your turn."; - -/* Label text for the Joined Waitlist state with notifications enabled */ -"windows-waitlist.joined.notifications-enabled" = "We’ll send you a notification when your copy of DuckDuckGo for Windows is ready for download."; - /* Title for the settings subtitle */ "windows-waitlist.settings.browse-privately" = "Browse privately with our app for Windows"; -/* Message used when sharing to iMessage. Parameter is an eight digit invite code. */ -"windows-waitlist.share-sheet.invite-code-message" = "You’re invited! - -Ready to use DuckDuckGo on Windows? - -Step 1 -Visit this URL on your Windows device to download: -https://duckduckgo.com/windows - -Step 2 -Open DuckDuckGo Installer in Downloads, select Install, then enter your invite code. - -Invite code: %@"; - /* Message used when sharing to iMessage */ "windows-waitlist.share-sheet.message" = "Ready to start browsing privately on Windows? diff --git a/DuckDuckGoTests/WindowsBrowserWaitlistTests.swift b/DuckDuckGoTests/WindowsBrowserWaitlistTests.swift deleted file mode 100644 index b7369ffdb0..0000000000 --- a/DuckDuckGoTests/WindowsBrowserWaitlistTests.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// WindowsBrowserWaitlistTests.swift -// DuckDuckGo -// -// Copyright © 2022 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 XCTest -import WaitlistMocks -@testable import DuckDuckGo -@testable import Core -import BrowserServicesKit - -class WindowsBrowserWaitlistTests: XCTestCase { - - func testWhenUserHasNotJoinedWaitlist_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - let request = MockWaitlistRequest.failure() - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: PrivacyConfigurationManagerMock()) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.windowsWaitlistBrowsePrivately) - } - - func testWhenUserIsOnWaitlist_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - store.store(waitlistToken: "abcd") - store.store(waitlistTimestamp: 12345) - - let request = MockWaitlistRequest.failure() - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: PrivacyConfigurationManagerMock()) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.waitlistOnTheList) - } - - func testWhenUserIsInvited_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - store.store(inviteCode: "code") - - let request = MockWaitlistRequest.failure() - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: PrivacyConfigurationManagerMock()) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.waitlistDownloadAvailable) - } - - func testWhenWindowsDownloadLinkEnabled_ThenSettingsSubtitleIsCorrect() { - let store = MockWaitlistStorage() - store.store(inviteCode: "code") - - let request = MockWaitlistRequest.failure() - let privacyConfigurationManager: PrivacyConfigurationManagerMock = PrivacyConfigurationManagerMock() - let privacyConfig = privacyConfigurationManager.privacyConfig as! PrivacyConfigurationMock // swiftlint:disable:this force_cast - privacyConfig.enabledFeaturesForVersions[.windowsDownloadLink] = Set([AppVersionProvider().appVersion()!]) - - let waitlist = WindowsBrowserWaitlist(store: store, request: request, privacyConfigurationManager: privacyConfigurationManager) - - XCTAssertEqual(waitlist.settingsSubtitle, UserText.windowsWaitlistBrowsePrivately) - } - -} diff --git a/LocalPackages/DuckUI/Sources/DuckUI/Color.swift b/LocalPackages/DuckUI/Sources/DuckUI/Color.swift index 36c2fe13ad..c735ac88e7 100644 --- a/LocalPackages/DuckUI/Sources/DuckUI/Color.swift +++ b/LocalPackages/DuckUI/Sources/DuckUI/Color.swift @@ -108,7 +108,7 @@ public extension UIColor { } -private extension Color { +public extension Color { init(_ hex: UInt, alpha: Double = 1) { self.init( .sRGB, From 82a8432ea6c5b0886808bc5be7c6ba9013b29064 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 14 Feb 2024 15:11:46 +0100 Subject: [PATCH 030/245] Move subscription Code to BSK (#2473) Task/Issue URL: https://app.asana.com/0/414235014887631/1206582087819536/f Description: Moves Subscriptions to BSK --- DuckDuckGo.xcodeproj/project.pbxproj | 122 ++------ .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 4 + DuckDuckGo/DesktopDownloadView.swift | 1 + DuckDuckGo/MainViewController+Segues.swift | 13 +- DuckDuckGo/MainViewController.swift | 4 + DuckDuckGo/SettingsHostingController.swift | 3 + DuckDuckGo/SettingsSubscriptionView.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 33 ++- .../Subscription/AccountManager.swift | 211 ------------- .../AccountKeychainStorage.swift | 196 ------------ .../AccountStorage/AccountStorage.swift | 32 -- .../AppStoreAccountManagementFlow.swift | 57 ---- .../Flows/AppStore/AppStorePurchaseFlow.swift | 139 --------- .../Flows/AppStore/AppStoreRestoreFlow.swift | 93 ------ .../Subscription/Flows/PurchaseFlow.swift | 73 ----- .../Subscription/Subscription/Logging.swift | 57 ---- .../Subscription/PurchaseManager.swift | 278 ------------------ .../Subscription/Services/APIService.swift | 111 ------- .../Subscription/Services/AuthService.swift | 117 -------- .../Services/SubscriptionService.swift | 91 ------ .../SubscriptionPurchaseEnvironment.swift | 66 ----- .../Subscription/URL+Subscription.swift | 59 ---- ...IdentityTheftRestorationPagesFeature.swift | 1 + ...scriptionPagesUseSubscriptionFeature.swift | 1 + .../SubscriptionEmailViewModel.swift | 1 + .../ViewModel/SubscriptionFlowViewModel.swift | 4 +- .../ViewModel/SubscriptionITPViewModel.swift | 5 +- .../SubscriptionRestoreViewModel.swift | 1 + .../SubscriptionSettingsViewModel.swift | 3 +- .../SubscriptionDebugViewController.swift | 2 + LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/Subscription/.gitignore | 8 - LocalPackages/Subscription/Package.resolved | 104 ------- LocalPackages/Subscription/Package.swift | 27 -- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 37 files changed, 85 insertions(+), 1843 deletions(-) delete mode 100644 DuckDuckGo/Subscription/Subscription/AccountManager.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/AccountStorage/AccountKeychainStorage.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/AccountStorage/AccountStorage.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Flows/PurchaseFlow.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Logging.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/PurchaseManager.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Services/APIService.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Services/AuthService.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/Services/SubscriptionService.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/SubscriptionPurchaseEnvironment.swift delete mode 100644 DuckDuckGo/Subscription/Subscription/URL+Subscription.swift delete mode 100644 LocalPackages/Subscription/.gitignore delete mode 100644 LocalPackages/Subscription/Package.resolved delete mode 100644 LocalPackages/Subscription/Package.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5a4dbfe3b6..4902911bc7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -394,7 +394,6 @@ 853A717820F645FB00FE60BC /* PixelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853A717720F645FB00FE60BC /* PixelTests.swift */; }; 853C5F5B21BFF0AE001F7A05 /* HomeCollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853C5F5A21BFF0AE001F7A05 /* HomeCollectionView.swift */; }; 853C5F6121C277C7001F7A05 /* global.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853C5F6021C277C7001F7A05 /* global.swift */; }; - 854007E72B57FC000001BD98 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 854007E62B57FC000001BD98 /* ZIPFoundation */; }; 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BBA12440857A00017FE4 /* PreserveLoginsWorker.swift */; }; 8540BD5223D8C2220057FDD2 /* PreserveLoginsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */; }; 8540BD5423D8D5080057FDD2 /* PreserveLoginsAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5323D8D5080057FDD2 /* PreserveLoginsAlert.swift */; }; @@ -765,6 +764,8 @@ CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */; }; + D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA152B7CF77300A0FBB9 /* Subscription */; }; + D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */; }; D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; }; @@ -790,20 +791,6 @@ D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; - D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8B2B291CA90054390C /* URL+Subscription.swift */; }; - D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */; }; - D6D12CA12B291CA90054390C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8D2B291CA90054390C /* Logging.swift */; }; - D6D12CA22B291CA90054390C /* AccountManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C8E2B291CA90054390C /* AccountManager.swift */; }; - D6D12CA32B291CAA0054390C /* AccountKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */; }; - D6D12CA42B291CAA0054390C /* AccountStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C912B291CA90054390C /* AccountStorage.swift */; }; - D6D12CA52B291CAA0054390C /* AppStorePurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */; }; - D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */; }; - D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */; }; - D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C972B291CA90054390C /* PurchaseFlow.swift */; }; - D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9B2B291CA90054390C /* SubscriptionService.swift */; }; - D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9C2B291CA90054390C /* APIService.swift */; }; - D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9D2B291CA90054390C /* AuthService.swift */; }; - D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D12C9E2B291CA90054390C /* PurchaseManager.swift */; }; D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; D6E0C1832B7A2B1E00D5E1E9 /* DesktopDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */; }; @@ -2451,20 +2438,6 @@ D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; - D6D12C8B2B291CA90054390C /* URL+Subscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URL+Subscription.swift"; sourceTree = ""; }; - D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionPurchaseEnvironment.swift; sourceTree = ""; }; - D6D12C8D2B291CA90054390C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; - D6D12C8E2B291CA90054390C /* AccountManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountManager.swift; sourceTree = ""; }; - D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountKeychainStorage.swift; sourceTree = ""; }; - D6D12C912B291CA90054390C /* AccountStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountStorage.swift; sourceTree = ""; }; - D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStorePurchaseFlow.swift; sourceTree = ""; }; - D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreRestoreFlow.swift; sourceTree = ""; }; - D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStoreAccountManagementFlow.swift; sourceTree = ""; }; - D6D12C972B291CA90054390C /* PurchaseFlow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseFlow.swift; sourceTree = ""; }; - D6D12C9B2B291CA90054390C /* SubscriptionService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionService.swift; sourceTree = ""; }; - D6D12C9C2B291CA90054390C /* APIService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APIService.swift; sourceTree = ""; }; - D6D12C9D2B291CA90054390C /* AuthService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthService.swift; sourceTree = ""; }; - D6D12C9E2B291CA90054390C /* PurchaseManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchaseManager.swift; sourceTree = ""; }; D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadView.swift; sourceTree = ""; }; @@ -2716,7 +2689,6 @@ F4D7F634298C00C3006C3AE9 /* FindInPageIOSJSSupport in Frameworks */, 85D598872927F84C00FA3B1B /* Crashes in Frameworks */, D664C7DD2B28A02800CBFA76 /* StoreKit.framework in Frameworks */, - 854007E72B57FC000001BD98 /* ZIPFoundation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2787,12 +2759,14 @@ F486D33425069BBB002D07D7 /* Kingfisher in Frameworks */, EE8E568A2A56BCE400F11DCA /* NetworkProtection in Frameworks */, CBC83E3429B631780008E19C /* Configuration in Frameworks */, + D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */, 98A16C2D28A11D6200A6C003 /* BrowserServicesKit in Frameworks */, 8599690F29D2F1C100DBF9FA /* DDGSync in Frameworks */, 1E60989F290011E600A508F9 /* PrivacyDashboard in Frameworks */, 851481882A600EFC00ABC65F /* RemoteMessaging in Frameworks */, 37DF000C29F9CA80002B7D3E /* SyncDataProviders in Frameworks */, 1E6098A1290011E600A508F9 /* UserScript in Frameworks */, + D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */, C14882ED27F211A000D59F0C /* SwiftSoup in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4519,7 +4493,6 @@ D664C7AC2B289AA000CBFA76 /* Views */, D664C7B02B289AA000CBFA76 /* UserScripts */, D664C7962B289AA000CBFA76 /* Extensions */, - D6D12C8A2B291CA90054390C /* Subscription */, D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, ); path = Subscription; @@ -4574,59 +4547,6 @@ path = UserScripts; sourceTree = ""; }; - D6D12C8A2B291CA90054390C /* Subscription */ = { - isa = PBXGroup; - children = ( - D6D12C8B2B291CA90054390C /* URL+Subscription.swift */, - D6D12C8C2B291CA90054390C /* SubscriptionPurchaseEnvironment.swift */, - D6D12C8D2B291CA90054390C /* Logging.swift */, - D6D12C8E2B291CA90054390C /* AccountManager.swift */, - D6D12C8F2B291CA90054390C /* AccountStorage */, - D6D12C922B291CA90054390C /* Flows */, - D6D12C9A2B291CA90054390C /* Services */, - D6D12C9E2B291CA90054390C /* PurchaseManager.swift */, - ); - path = Subscription; - sourceTree = ""; - }; - D6D12C8F2B291CA90054390C /* AccountStorage */ = { - isa = PBXGroup; - children = ( - D6D12C902B291CA90054390C /* AccountKeychainStorage.swift */, - D6D12C912B291CA90054390C /* AccountStorage.swift */, - ); - path = AccountStorage; - sourceTree = ""; - }; - D6D12C922B291CA90054390C /* Flows */ = { - isa = PBXGroup; - children = ( - D6D12C932B291CA90054390C /* AppStore */, - D6D12C972B291CA90054390C /* PurchaseFlow.swift */, - ); - path = Flows; - sourceTree = ""; - }; - D6D12C932B291CA90054390C /* AppStore */ = { - isa = PBXGroup; - children = ( - D6D12C942B291CA90054390C /* AppStorePurchaseFlow.swift */, - D6D12C952B291CA90054390C /* AppStoreRestoreFlow.swift */, - D6D12C962B291CA90054390C /* AppStoreAccountManagementFlow.swift */, - ); - path = AppStore; - sourceTree = ""; - }; - D6D12C9A2B291CA90054390C /* Services */ = { - isa = PBXGroup; - children = ( - D6D12C9B2B291CA90054390C /* SubscriptionService.swift */, - D6D12C9C2B291CA90054390C /* APIService.swift */, - D6D12C9D2B291CA90054390C /* AuthService.swift */, - ); - path = Services; - sourceTree = ""; - }; D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */ = { isa = PBXGroup; children = ( @@ -5728,7 +5648,6 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, - 854007E62B57FC000001BD98 /* ZIPFoundation */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -5910,6 +5829,8 @@ 37DF000B29F9CA80002B7D3E /* SyncDataProviders */, 851481872A600EFC00ABC65F /* RemoteMessaging */, EE8E56892A56BCE400F11DCA /* NetworkProtection */, + D61CDA152B7CF77300A0FBB9 /* Subscription */, + D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -6508,7 +6429,6 @@ 1E24295E293F57FA00584836 /* LottieView.swift in Sources */, 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */, 4BB697A42B1D99C4003699B5 /* VPNWaitlistActivationDateStore.swift in Sources */, - D6D12CAD2B291CAA0054390C /* PurchaseManager.swift in Sources */, 853C5F5B21BFF0AE001F7A05 /* HomeCollectionView.swift in Sources */, 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, @@ -6532,7 +6452,6 @@ D6E83C122B1E6AB3006C8AFB /* SettingsView.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, - D6D12CA62B291CAA0054390C /* AppStoreRestoreFlow.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */, F1CA3C371F045878005FADB3 /* PrivacyStore.swift in Sources */, @@ -6555,9 +6474,7 @@ D664C7CE2B289AA200CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift in Sources */, EE9D68DC2AE16AE100B55EF4 /* NotificationsAuthorizationController.swift in Sources */, AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */, - D6D12CA42B291CAA0054390C /* AccountStorage.swift in Sources */, D6E83C2E2B1EA06E006C8AFB /* SettingsViewModel.swift in Sources */, - D6D12CA32B291CAA0054390C /* AccountKeychainStorage.swift in Sources */, 8590CB612684D0600089F6BF /* CookieDebugViewController.swift in Sources */, 319A37152829A55F0079FBCE /* AutofillListItemTableViewCell.swift in Sources */, 1EA513782866039400493C6A /* TrackerAnimationLogic.swift in Sources */, @@ -6597,7 +6514,6 @@ 020108A929A7C1CD00644F9D /* AppTrackerImageCache.swift in Sources */, 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */, 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, - D6D12CA82B291CAA0054390C /* PurchaseFlow.swift in Sources */, C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, @@ -6627,7 +6543,6 @@ 8505836F219F424500ED4EDB /* UIViewExtension.swift in Sources */, D6F93E3C2B4FFA97004C268D /* SubscriptionDebugViewController.swift in Sources */, 8505836E219F424500ED4EDB /* RoundedRectangleView.swift in Sources */, - D6D12CA12B291CA90054390C /* Logging.swift in Sources */, EE8594992A44791C008A6D06 /* NetworkProtectionTunnelController.swift in Sources */, 1EEF123F2850A68A003DDE57 /* PrivacyInfoContainerView.swift in Sources */, F4B0B796252CB35700830156 /* OnboardingWidgetsDetailsViewController.swift in Sources */, @@ -6671,7 +6586,6 @@ D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, - D6D12CA22B291CA90054390C /* AccountManager.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, 83BE9BC3215D69C1009844D9 /* AppConfigurationFetch.swift in Sources */, 1EEC460627A9499600E75FCB /* DownloadsList.swift in Sources */, @@ -6679,7 +6593,6 @@ 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, - D6D12CA52B291CAA0054390C /* AppStorePurchaseFlow.swift in Sources */, D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */, F4F6DFB426E6B63700ED7E12 /* BookmarkFolderCell.swift in Sources */, D6F93E3E2B50A8A0004C268D /* SubscriptionSettingsView.swift in Sources */, @@ -6689,7 +6602,6 @@ 3157B43327F497E90042D3D7 /* SaveLoginView.swift in Sources */, F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */, 0290472529E8496A0008FE3C /* AppTPActivityIconView.swift in Sources */, - D6D12CA72B291CAA0054390C /* AppStoreAccountManagementFlow.swift in Sources */, D664C7C82B289AA200CBFA76 /* SubscriptionFlowView.swift in Sources */, EE458D142ABB652900FC651A /* NetworkProtectionDebugUtilities.swift in Sources */, 8528AE7C212EF4A200D0BD74 /* AppRatingPrompt.swift in Sources */, @@ -6733,7 +6645,6 @@ 4BBBBA8D2B031B4200D965DA /* VPNWaitlistDebugViewController.swift in Sources */, C160544129D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift in Sources */, 02A54A9A2A094A17000C8FED /* AppTPHomeView.swift in Sources */, - D6D12C9F2B291CA90054390C /* URL+Subscription.swift in Sources */, 31C70B5528045E3500FB6AD1 /* SecureVaultErrorReporter.swift in Sources */, F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */, 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, @@ -6754,7 +6665,6 @@ 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, - D6D12CAB2B291CAA0054390C /* APIService.swift in Sources */, 989B337522D7EF2100437824 /* EmptyCollectionReusableView.swift in Sources */, 8524CC94246C5C8900E59D45 /* DaxDialogViewController.swift in Sources */, F42EF9312614BABE00101FB9 /* ActionSheetDaxDialogViewController.swift in Sources */, @@ -6785,7 +6695,6 @@ 3158461A281B08F5004ADB8B /* AutofillLoginListViewModel.swift in Sources */, 31C138A827A3E9C900FFD4B2 /* URLDownloadSession.swift in Sources */, 981FED76220464EF008488D7 /* AutoClearSettingsModel.swift in Sources */, - D6D12CA02B291CA90054390C /* SubscriptionPurchaseEnvironment.swift in Sources */, 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */, 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */, CBD4F140279EBFB300B20FD7 /* SwiftUICollectionViewCell.swift in Sources */, @@ -6859,7 +6768,6 @@ 9872D205247DCAC100CEF398 /* TabPreviewsSource.swift in Sources */, F130D73A1E5776C500C45811 /* OmniBarDelegate.swift in Sources */, 85DFEDEF24C7EA3B00973FE7 /* SmallOmniBarState.swift in Sources */, - D6D12CAC2B291CAA0054390C /* AuthService.swift in Sources */, 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, @@ -6949,7 +6857,6 @@ F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, - D6D12CAA2B291CAA0054390C /* SubscriptionService.swift in Sources */, D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, C1B7B52428941F2A0098FD6A /* RemoteMessageRequest.swift in Sources */, EE9D68DA2AE1659F00B55EF4 /* NetworkProtectionVPNNotificationsViewModel.swift in Sources */, @@ -9985,7 +9892,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 106.0.1; + version = 107.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { @@ -10112,11 +10019,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = RemoteMessaging; }; - 854007E62B57FC000001BD98 /* ZIPFoundation */ = { - isa = XCSwiftPackageProductDependency; - package = 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; - productName = ZIPFoundation; - }; 85875B6029912A9900115F05 /* SyncUI */ = { isa = XCSwiftPackageProductDependency; productName = SyncUI; @@ -10206,6 +10108,16 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Configuration; }; + D61CDA152B7CF77300A0FBB9 /* Subscription */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Subscription; + }; + D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */ = { + isa = XCSwiftPackageProductDependency; + package = 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; + productName = ZIPFoundation; + }; EE8E56892A56BCE400F11DCA /* NetworkProtection */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7e74a98bef..ae8e29e12e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "3f5e33ec3d75dd2c130cfc6915c0a6e8efeb96f1", - "version" : "106.0.1" + "revision" : "328ce451fd1593809d1470ab5a0b5242a595f88c", + "version" : "107.0.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index f84dedc923..c9bfe95a32 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -34,6 +34,10 @@ import Networking import DDGSync import SyncDataProviders +#if SUBSCRIPTION +import Subscription +#endif + #if NETWORK_PROTECTION import NetworkProtection import WebKit diff --git a/DuckDuckGo/DesktopDownloadView.swift b/DuckDuckGo/DesktopDownloadView.swift index f089c1b21b..42d43e3fc2 100644 --- a/DuckDuckGo/DesktopDownloadView.swift +++ b/DuckDuckGo/DesktopDownloadView.swift @@ -63,6 +63,7 @@ struct DesktopDownloadView: View { } } ) + // XAI: Move all strings to a Constants enum at the top .buttonStyle(DesktopDownloadViewButtonStyle(enabled: true)) .padding(.horizontal, padding) .padding(.top, 24) diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 94b4be4591..c1e8a9560f 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -23,7 +23,10 @@ import Core import Bookmarks import BrowserServicesKit import SwiftUI -import PrivacyDashboard + +#if SUBSCRIPTION +import Subscription +#endif extension MainViewController { @@ -235,15 +238,19 @@ extension MainViewController { syncDataProviders: syncDataProviders, appSettings: appSettings, bookmarksDatabase: bookmarksDatabase) - +#if SUBSCRIPTION let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, accountManager: AccountManager()) +#else + let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider) +#endif + let settingsController = SettingsHostingController(viewModel: settingsViewModel, viewProvider: legacyViewProvider) settingsController.applyTheme(ThemeManager.shared.currentTheme) // We are still presenting legacy views, so use a Navcontroller let navController = UINavigationController(rootViewController: settingsController) navController.applyTheme(ThemeManager.shared.currentTheme) - settingsController.modalPresentationStyle = .automatic + settingsController.modalPresentationStyle = UIModalPresentationStyle.automatic present(navController, animated: true) { completion?(settingsViewModel) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 697640fce6..8799d48c8b 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -31,6 +31,10 @@ import Persistence import PrivacyDashboard import Networking +#if SUBSCRIPTION +import Subscription +#endif + #if NETWORK_PROTECTION import NetworkProtection #endif diff --git a/DuckDuckGo/SettingsHostingController.swift b/DuckDuckGo/SettingsHostingController.swift index 8bbce4186e..7bf9e3c242 100644 --- a/DuckDuckGo/SettingsHostingController.swift +++ b/DuckDuckGo/SettingsHostingController.swift @@ -19,6 +19,9 @@ import UIKit import SwiftUI +#if SUBSCRIPTION +import Subscription +#endif class SettingsHostingController: UIHostingController { var viewModel: SettingsViewModel diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index d03b65ce41..02b5acda25 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -21,6 +21,7 @@ import SwiftUI import UIKit #if SUBSCRIPTION +import Subscription @available(iOS 15.0, *) struct SettingsSubscriptionView: View { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index f4f426b1fe..0e79e5475e 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -25,6 +25,10 @@ import Common import Combine import SyncUI +#if SUBSCRIPTION +import Subscription +#endif + #if APP_TRACKING_PROTECTION import NetworkExtension #endif @@ -42,9 +46,12 @@ final class SettingsViewModel: ObservableObject { private lazy var animator: FireButtonAnimator = FireButtonAnimator(appSettings: AppUserDefaults()) private var legacyViewProvider: SettingsLegacyViewProvider private lazy var versionProvider: AppVersion = AppVersion.shared - private var accountManager: AccountManager private let voiceSearchHelper: VoiceSearchHelperProtocol - +#if SUBSCRIPTION + private var accountManager: AccountManager +#endif + + #if NETWORK_PROTECTION private let connectionObserver = ConnectionStatusObserverThroughSession() #endif @@ -56,7 +63,7 @@ final class SettingsViewModel: ObservableObject { // Defaults @UserDefaultsWrapper(key: .subscriptionIsActive, defaultValue: false) static private var cachedHasActiveSubscription: Bool - + // Closures to interact with legacy view controllers through the container var onRequestPushLegacyView: ((UIViewController) -> Void)? var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? @@ -64,7 +71,7 @@ final class SettingsViewModel: ObservableObject { var onRequestDismissSettings: (() -> Void)? // SwiftUI Programatic Navigation Variables - // Add more views as needed here... + // Add more views as needed here... @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false @@ -86,7 +93,7 @@ final class SettingsViewModel: ObservableObject { case networkProtection #endif } - + var shouldShowNoMicrophonePermissionAlert: Bool = false // Used to automatically navigate on Appear to a specific section @@ -194,7 +201,7 @@ final class SettingsViewModel: ObservableObject { } ) } - +#if SUBSCRIPTION // MARK: Default Init init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider, @@ -207,8 +214,20 @@ final class SettingsViewModel: ObservableObject { self.voiceSearchHelper = voiceSearchHelper self.onAppearNavigationTarget = navigateOnAppearDestination } +#else + // MARK: Default Init + init(state: SettingsState? = nil, + legacyViewProvider: SettingsLegacyViewProvider, + voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper, + navigateOnAppearDestination: SettingsSection = .none) { + self.state = SettingsState.defaults + self.legacyViewProvider = legacyViewProvider + self.voiceSearchHelper = voiceSearchHelper + self.onAppearNavigationTarget = navigateOnAppearDestination + } +#endif + } - // MARK: Private methods extension SettingsViewModel { diff --git a/DuckDuckGo/Subscription/Subscription/AccountManager.swift b/DuckDuckGo/Subscription/Subscription/AccountManager.swift deleted file mode 100644 index 60b87a9d65..0000000000 --- a/DuckDuckGo/Subscription/Subscription/AccountManager.swift +++ /dev/null @@ -1,211 +0,0 @@ -// -// AccountManager.swift -// DuckDuckGo -// -// Copyright © 2023 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 Common - -public extension Notification.Name { - static let accountDidSignIn = Notification.Name("com.duckduckgo.browserServicesKit.AccountDidSignIn") - static let accountDidSignOut = Notification.Name("com.duckduckgo.browserServicesKit.AccountDidSignOut") -} - -public protocol AccountManagerKeychainAccessDelegate: AnyObject { - func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) -} - -public class AccountManager { - - private let storage: AccountStorage - public weak var delegate: AccountManagerKeychainAccessDelegate? - - public var isUserAuthenticated: Bool { - return accessToken != nil - } - - public init(storage: AccountStorage = AccountKeychainStorage()) { - self.storage = storage - } - - public var authToken: String? { - do { - return try storage.getAuthToken() - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .getAuthToken, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - - return nil - } - } - - public var accessToken: String? { - do { - return try storage.getAccessToken() - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .getAccessToken, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - - return nil - } - } - - public var email: String? { - do { - return try storage.getEmail() - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .getEmail, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - - return nil - } - } - - public var externalID: String? { - do { - return try storage.getExternalID() - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .getExternalID, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - - return nil - } - } - - public func storeAuthToken(token: String) { - do { - try storage.store(authToken: token) - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .storeAuthToken, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - } - } - - public func storeAccount(token: String, email: String?, externalID: String?) { - do { - try storage.store(accessToken: token) - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .storeAccessToken, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - } - - do { - try storage.store(email: email) - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .storeEmail, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - } - - do { - try storage.store(externalID: externalID) - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .storeExternalID, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - } - NotificationCenter.default.post(name: .accountDidSignIn, object: self, userInfo: nil) - } - - public func signOut() { - do { - try storage.clearAuthenticationState() - } catch { - if let error = error as? AccountKeychainAccessError { - delegate?.accountManagerKeychainAccessFailed(accessType: .clearAuthenticationData, error: error) - } else { - assertionFailure("Expected AccountKeychainAccessError") - } - } - - NotificationCenter.default.post(name: .accountDidSignOut, object: self, userInfo: nil) - } - - // MARK: - - - public func hasEntitlement(for name: String) async -> Bool { - await fetchEntitlements().contains(name) - } - - public func fetchEntitlements() async -> [String] { - guard let accessToken else { return [] } - - switch await AuthService.validateToken(accessToken: accessToken) { - case .success(let response): - let entitlements = response.account.entitlements - return entitlements.map { $0.name } - - case .failure(let error): - os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) - return [] - } - } - - public func exchangeAuthTokenToAccessToken(_ authToken: String) async -> Result { - switch await AuthService.getAccessToken(token: authToken) { - case .success(let response): - return .success(response.accessToken) - case .failure(let error): - os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) - return .failure(error) - } - } - - public typealias AccountDetails = (email: String?, externalID: String) - - public func fetchAccountDetails(with accessToken: String) async -> Result { - switch await AuthService.validateToken(accessToken: accessToken) { - case .success(let response): - return .success(AccountDetails(email: response.account.email, externalID: response.account.externalID)) - case .failure(let error): - os_log("AccountManager error: %{public}@", log: .error, error.localizedDescription) - return .failure(error) - } - } - - public func checkSubscriptionState() async { - guard let token = accessToken else { return } - - if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { - if !response.isSubscriptionActive { - signOut() - } - } - } -} diff --git a/DuckDuckGo/Subscription/Subscription/AccountStorage/AccountKeychainStorage.swift b/DuckDuckGo/Subscription/Subscription/AccountStorage/AccountKeychainStorage.swift deleted file mode 100644 index 5fc79bc636..0000000000 --- a/DuckDuckGo/Subscription/Subscription/AccountStorage/AccountKeychainStorage.swift +++ /dev/null @@ -1,196 +0,0 @@ -// -// AccountKeychainStorage.swift -// DuckDuckGo -// -// Copyright © 2023 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 - -public enum AccountKeychainAccessType: String { - case getAuthToken - case storeAuthToken - case getAccessToken - case storeAccessToken - case getEmail - case storeEmail - case getExternalID - case storeExternalID - case clearAuthenticationData -} - -public enum AccountKeychainAccessError: Error, Equatable { - case failedToDecodeKeychainValueAsData - case failedToDecodeKeychainDataAsString - case keychainSaveFailure(OSStatus) - case keychainDeleteFailure(OSStatus) - case keychainLookupFailure(OSStatus) - - public var errorDescription: String { - switch self { - case .failedToDecodeKeychainValueAsData: return "failedToDecodeKeychainValueAsData" - case .failedToDecodeKeychainDataAsString: return "failedToDecodeKeychainDataAsString" - case .keychainSaveFailure: return "keychainSaveFailure" - case .keychainDeleteFailure: return "keychainDeleteFailure" - case .keychainLookupFailure: return "keychainLookupFailure" - } - } -} - -public class AccountKeychainStorage: AccountStorage { - - public init() {} - - public func getAuthToken() throws -> String? { - try Self.getString(forField: .authToken) - } - - public func store(authToken: String) throws { - try Self.set(string: authToken, forField: .authToken) - } - - public func getAccessToken() throws -> String? { - try Self.getString(forField: .accessToken) - } - - public func store(accessToken: String) throws { - try Self.set(string: accessToken, forField: .accessToken) - } - - public func getEmail() throws -> String? { - try Self.getString(forField: .email) - } - - public func getExternalID() throws -> String? { - try Self.getString(forField: .externalID) - } - - public func store(externalID: String?) throws { - if let externalID = externalID, !externalID.isEmpty { - try Self.set(string: externalID, forField: .externalID) - } else { - try Self.deleteItem(forField: .externalID) - } - } - - public func store(email: String?) throws { - if let email = email, !email.isEmpty { - try Self.set(string: email, forField: .email) - } else { - try Self.deleteItem(forField: .email) - } - } - - public func clearAuthenticationState() throws { - try Self.deleteItem(forField: .authToken) - try Self.deleteItem(forField: .accessToken) - try Self.deleteItem(forField: .email) - try Self.deleteItem(forField: .externalID) - } - -} - -private extension AccountKeychainStorage { - - /* - Uses just kSecAttrService as the primary key, since we don't want to store - multiple accounts/tokens at the same time - */ - enum AccountKeychainField: String, CaseIterable { - case authToken = "account.authToken" - case accessToken = "account.accessToken" - case email = "account.email" - case externalID = "account.external_id" - - var keyValue: String { - (Bundle.main.bundleIdentifier ?? "com.duckduckgo") + "." + rawValue - } - } - - static func getString(forField field: AccountKeychainField) throws -> String? { - guard let data = try retrieveData(forField: field) else { - return nil - } - - if let decodedString = String(data: data, encoding: String.Encoding.utf8) { - return decodedString - } else { - throw AccountKeychainAccessError.failedToDecodeKeychainDataAsString - } - } - - static func retrieveData(forField field: AccountKeychainField, useDataProtectionKeychain: Bool = true) throws -> Data? { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecMatchLimit as String: kSecMatchLimitOne, - kSecAttrService as String: field.keyValue, - kSecReturnData as String: true, - kSecUseDataProtectionKeychain as String: useDataProtectionKeychain - ] - - var item: CFTypeRef? - let status = SecItemCopyMatching(query as CFDictionary, &item) - - if status == errSecSuccess { - if let existingItem = item as? Data { - return existingItem - } else { - throw AccountKeychainAccessError.failedToDecodeKeychainValueAsData - } - } else if status == errSecItemNotFound { - return nil - } else { - throw AccountKeychainAccessError.keychainLookupFailure(status) - } - } - - static func set(string: String, forField field: AccountKeychainField) throws { - guard let stringData = string.data(using: .utf8) else { - return - } - - try deleteItem(forField: field) - try store(data: stringData, forField: field) - } - - static func store(data: Data, forField field: AccountKeychainField, useDataProtectionKeychain: Bool = true) throws { - let query = [ - kSecClass: kSecClassGenericPassword, - kSecAttrSynchronizable: false, - kSecAttrService: field.keyValue, - kSecAttrAccessible: kSecAttrAccessibleAfterFirstUnlock, - kSecValueData: data, - kSecUseDataProtectionKeychain: useDataProtectionKeychain] as [String: Any] - - let status = SecItemAdd(query as CFDictionary, nil) - - if status != errSecSuccess { - throw AccountKeychainAccessError.keychainSaveFailure(status) - } - } - - static func deleteItem(forField field: AccountKeychainField, useDataProtectionKeychain: Bool = true) throws { - let query: [String: Any] = [ - kSecClass as String: kSecClassGenericPassword, - kSecAttrService as String: field.keyValue, - kSecUseDataProtectionKeychain as String: useDataProtectionKeychain] - - let status = SecItemDelete(query as CFDictionary) - - if status != errSecSuccess && status != errSecItemNotFound { - throw AccountKeychainAccessError.keychainDeleteFailure(status) - } - } -} diff --git a/DuckDuckGo/Subscription/Subscription/AccountStorage/AccountStorage.swift b/DuckDuckGo/Subscription/Subscription/AccountStorage/AccountStorage.swift deleted file mode 100644 index 9a1c98d6b7..0000000000 --- a/DuckDuckGo/Subscription/Subscription/AccountStorage/AccountStorage.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// AccountStorage.swift -// DuckDuckGo -// -// Copyright © 2023 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 - -public protocol AccountStorage: AnyObject { - func getAuthToken() throws -> String? - func store(authToken: String) throws - func getAccessToken() throws -> String? - func store(accessToken: String) throws - func getEmail() throws -> String? - func store(email: String?) throws - func getExternalID() throws -> String? - func store(externalID: String?) throws - func clearAuthenticationState() throws -} diff --git a/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift b/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift deleted file mode 100644 index 4c199acf20..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreAccountManagementFlow.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// AppStoreAccountManagementFlow.swift -// DuckDuckGo -// -// Copyright © 2023 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 StoreKit - -public final class AppStoreAccountManagementFlow { - - public enum Error: Swift.Error { - case noPastTransaction - case authenticatingWithTransactionFailed - } - - @discardableResult - public static func refreshAuthTokenIfNeeded() async -> Result { - var authToken = AccountManager().authToken ?? "" - - // Check if auth token if still valid - if case let .failure(error) = await AuthService.validateToken(accessToken: authToken) { - print(error) - - if #available(macOS 12.0, iOS 15.0, *) { - // In case of invalid token attempt store based authentication to obtain a new one - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { - return .failure(.noPastTransaction) } - - switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { - case .success(let response): - if response.externalID == AccountManager().externalID { - authToken = response.authToken - AccountManager().storeAuthToken(token: authToken) - } - case .failure: - return .failure(.authenticatingWithTransactionFailed) - } - } - } - - return .success(authToken) - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift b/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift deleted file mode 100644 index 28a3c0cfdd..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStorePurchaseFlow.swift +++ /dev/null @@ -1,139 +0,0 @@ -// -// AppStorePurchaseFlow.swift -// DuckDuckGo -// -// Copyright © 2023 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 StoreKit - -@available(macOS 12.0, iOS 15.0, *) -public final class AppStorePurchaseFlow { - - public enum Error: Swift.Error { - case noProductsFound - - case activeSubscriptionAlreadyPresent - case authenticatingWithTransactionFailed - case accountCreationFailed - case purchaseFailed - - case missingEntitlements - - case somethingWentWrong - } - - public static func subscriptionOptions() async -> Result { - - let products = PurchaseManager.shared.availableProducts - - let monthly = products.first(where: { $0.id.contains("1month") }) - let yearly = products.first(where: { $0.id.contains("1year") }) - - guard let monthly, let yearly else { return .failure(.noProductsFound) } - - let options = [SubscriptionOption(id: monthly.id, cost: .init(displayPrice: monthly.displayPrice, recurrence: "monthly")), - SubscriptionOption(id: yearly.id, cost: .init(displayPrice: yearly.displayPrice, recurrence: "yearly"))] - - let features = SubscriptionFeatureName.allCases.map { SubscriptionFeature(name: $0.rawValue) } - - return .success(SubscriptionOptions(platform: SubscriptionPlatformName.macos.rawValue, - options: options, - features: features)) - } - - public static func purchaseSubscription(with subscriptionIdentifier: String, emailAccessToken: String?) async -> Result { - let accountManager = AccountManager() - let externalID: String - - // Check for past transactions most recent - switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { - case .success: - return .failure(.activeSubscriptionAlreadyPresent) - case .failure(let error): - switch error { - case .subscriptionExpired(let expiredAccountDetails): - externalID = expiredAccountDetails.externalID - accountManager.storeAuthToken(token: expiredAccountDetails.authToken) - accountManager.storeAccount(token: expiredAccountDetails.accessToken, - email: expiredAccountDetails.email, - externalID: expiredAccountDetails.externalID) - case .missingAccountOrTransactions, .pastTransactionAuthenticationError: - // No history, create new account - switch await AuthService.createAccount(emailAccessToken: emailAccessToken) { - case .success(let response): - externalID = response.externalID - - if case let .success(accessToken) = await accountManager.exchangeAuthTokenToAccessToken(response.authToken), - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAuthToken(token: response.authToken) - accountManager.storeAccount(token: accessToken, - email: accountDetails.email, - externalID: accountDetails.externalID) - } - case .failure: - return .failure(.accountCreationFailed) - } - default: - return .failure(.authenticatingWithTransactionFailed) - } - } - - // Make the purchase - switch await PurchaseManager.shared.purchaseSubscription(with: subscriptionIdentifier, externalID: externalID) { - case .success: - return .success(()) - case .failure(let error): - print("Something went wrong, reason: \(error)") - AccountManager().signOut() - return .failure(.purchaseFailed) - } - } - - @discardableResult - public static func completeSubscriptionPurchase() async -> Result { - - let result = await checkForEntitlements(wait: 2.0, retry: 10) - - return result ? .success(PurchaseUpdate(type: "completed")) : .failure(.missingEntitlements) - } - - @discardableResult - public static func checkForEntitlements(wait waitTime: Double, retry retryCount: Int) async -> Bool { - var count = 0 - var hasEntitlements = false - - repeat { - hasEntitlements = await !AccountManager().fetchEntitlements().isEmpty - - if hasEntitlements { - break - } else { - count += 1 - try? await Task.sleep(seconds: waitTime) - } - } while !hasEntitlements && count < retryCount - - return hasEntitlements - } -} - -extension Task where Success == Never, Failure == Never { - static func sleep(seconds: Double) async throws { - let duration = UInt64(seconds * 1_000_000_000) - try await Task.sleep(nanoseconds: duration) - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift b/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift deleted file mode 100644 index de10a8f3ab..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Flows/AppStore/AppStoreRestoreFlow.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// AppStoreRestoreFlow.swift -// DuckDuckGo -// -// Copyright © 2023 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 StoreKit - -@available(macOS 12.0, iOS 15.0, *) -public final class AppStoreRestoreFlow { - - // swiftlint:disable:next large_tuple - public typealias RestoredAccountDetails = (authToken: String, accessToken: String, externalID: String, email: String?) - - public enum Error: Swift.Error { - case missingAccountOrTransactions - case pastTransactionAuthenticationError - case failedToObtainAccessToken - case failedToFetchAccountDetails - case failedToFetchSubscriptionDetails - case subscriptionExpired(accountDetails: RestoredAccountDetails) - case somethingWentWrong - } - - public static func restoreAccountFromPastPurchase() async -> Result { - guard let lastTransactionJWSRepresentation = await PurchaseManager.mostRecentTransaction() else { - return .failure(.missingAccountOrTransactions) - } - - let accountManager = AccountManager() - - // Do the store login to get short-lived token - let authToken: String - - switch await AuthService.storeLogin(signature: lastTransactionJWSRepresentation) { - case .success(let response): - authToken = response.authToken - case .failure: - return .failure(.pastTransactionAuthenticationError) - } - - let accessToken: String - let email: String? - let externalID: String - - switch await accountManager.exchangeAuthTokenToAccessToken(authToken) { - case .success(let exchangedAccessToken): - accessToken = exchangedAccessToken - case .failure: - return .failure(.failedToObtainAccessToken) - } - - switch await accountManager.fetchAccountDetails(with: accessToken) { - case .success(let accountDetails): - email = accountDetails.email - externalID = accountDetails.externalID - case .failure: - return .failure(.failedToFetchAccountDetails) - } - - var isSubscriptionActive = false - - switch await SubscriptionService.getSubscriptionDetails(token: accessToken) { - case .success(let response): - isSubscriptionActive = response.isSubscriptionActive - case .failure: - return .failure(.somethingWentWrong) - } - - if isSubscriptionActive { - accountManager.storeAuthToken(token: authToken) - accountManager.storeAccount(token: accessToken, email: email, externalID: externalID) - return .success(()) - } else { - let details = RestoredAccountDetails(authToken: authToken, accessToken: accessToken, externalID: externalID, email: email) - return .failure(.subscriptionExpired(accountDetails: details)) - } - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Flows/PurchaseFlow.swift b/DuckDuckGo/Subscription/Subscription/Flows/PurchaseFlow.swift deleted file mode 100644 index e5f7bec453..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Flows/PurchaseFlow.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// PurchaseFlow.swift -// DuckDuckGo -// -// Copyright © 2023 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 PurchaseFlow { - -} - -public struct SubscriptionOptions: Encodable { - let platform: String - let options: [SubscriptionOption] - let features: [SubscriptionFeature] -} - -public struct SubscriptionOption: Encodable { - let id: String - let cost: SubscriptionOptionCost -} - -struct SubscriptionOptionCost: Encodable { - let displayPrice: String - let recurrence: String -} - -public struct SubscriptionFeature: Encodable { - let name: String -} - -// MARK: - - -public enum SubscriptionFeatureName: String, CaseIterable { - case privateBrowsing = "private-browsing" - case privateSearch = "private-search" - case emailProtection = "email-protection" - case appTrackingProtection = "app-tracking-protection" - case vpn = "vpn" - case personalInformationRemoval = "personal-information-removal" - case identityTheftRestoration = "identity-theft-restoration" -} - -public enum SubscriptionPlatformName: String { - case macos - case stripe -} - -// MARK: - - -public struct PurchaseUpdate: Codable { - let type: String - let token: String? - - public init(type: String, token: String? = nil) { - self.type = type - self.token = token - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Logging.swift b/DuckDuckGo/Subscription/Subscription/Logging.swift deleted file mode 100644 index 24dc783c6e..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Logging.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Logging.swift -// DuckDuckGo -// -// Copyright © 2023 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 Common - -struct Logging { - - static let subsystem = "com.duckduckgo.macos.browser.account" - - fileprivate static let accountLoggingEnabled = true - fileprivate static let account: OSLog = OSLog(subsystem: subsystem, category: "Account") - - fileprivate static let authServiceLoggingEnabled = true - fileprivate static let authService: OSLog = OSLog(subsystem: subsystem, category: "Account : AuthService") - - fileprivate static let subscriptionServiceLoggingEnabled = true - fileprivate static let subscriptionService: OSLog = OSLog(subsystem: subsystem, category: "Account : SubscriptionService") - - fileprivate static let errorsLoggingEnabled = true - fileprivate static let error: OSLog = OSLog(subsystem: subsystem, category: "Account : Errors") -} - -extension OSLog { - - public static var account: OSLog { - Logging.accountLoggingEnabled ? Logging.account : .disabled - } - - public static var authService: OSLog { - Logging.authServiceLoggingEnabled ? Logging.authService : .disabled - } - - public static var subscriptionService: OSLog { - Logging.subscriptionServiceLoggingEnabled ? Logging.subscriptionService : .disabled - } - - public static var error: OSLog { - Logging.errorsLoggingEnabled ? Logging.error : .disabled - } -} diff --git a/DuckDuckGo/Subscription/Subscription/PurchaseManager.swift b/DuckDuckGo/Subscription/Subscription/PurchaseManager.swift deleted file mode 100644 index fcdb4d230b..0000000000 --- a/DuckDuckGo/Subscription/Subscription/PurchaseManager.swift +++ /dev/null @@ -1,278 +0,0 @@ -// -// PurchaseManager.swift -// DuckDuckGo -// -// Copyright © 2023 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 StoreKit - -@available(macOS 12.0, iOS 15.0, *) typealias Transaction = StoreKit.Transaction -@available(macOS 12.0, iOS 15.0, *) typealias RenewalInfo = StoreKit.Product.SubscriptionInfo.RenewalInfo -@available(macOS 12.0, iOS 15.0, *) typealias RenewalState = StoreKit.Product.SubscriptionInfo.RenewalState - -public enum StoreError: Error { - case failedVerification -} - -enum PurchaseManagerError: Error { - case productNotFound - case externalIDisNotAValidUUID - case purchaseFailed - case transactionCannotBeVerified - case transactionPendingAuthentication - case purchaseCancelledByUser - case unknownError -} - -@available(macOS 12.0, iOS 15.0, *) -public final class PurchaseManager: ObservableObject { - - static let productIdentifiers = ["ios.subscription.1month", "ios.subscription.1year", - "subscription.1week", "subscription.1month", "subscription.1year", - "review.subscription.1week", "review.subscription.1month", "review.subscription.1year"] - - public static let shared = PurchaseManager() - - @Published public private(set) var availableProducts: [Product] = [] - @Published public private(set) var purchasedProductIDs: [String] = [] - @Published public private(set) var purchaseQueue: [String] = [] - - @Published private(set) var subscriptionGroupStatus: RenewalState? - - private var transactionUpdates: Task? - private var storefrontChanges: Task? - - public init() { - transactionUpdates = observeTransactionUpdates() - storefrontChanges = observeStorefrontChanges() - } - - deinit { - transactionUpdates?.cancel() - storefrontChanges?.cancel() - } - - @MainActor - public func hasProductsAvailable() async -> Bool { - do { - let availableProducts = try await Product.products(for: Self.productIdentifiers) - print(" -- [PurchaseManager] updateAvailableProducts(): fetched \(availableProducts.count)") - return !availableProducts.isEmpty - } catch { - print("Error fetching available products: \(error)") - return false - } - } - - @MainActor - @discardableResult - public func syncAppleIDAccount() async -> Result { - do { - purchaseQueue.removeAll() - - print("Before AppStore.sync()") - - try await AppStore.sync() - - print("After AppStore.sync()") - - await updatePurchasedProducts() - await updateAvailableProducts() - - return .success(()) - } catch { - print("AppStore.sync error: \(error)") - return .failure(error) - } - } - - @MainActor - public func updateAvailableProducts() async { - print(" -- [PurchaseManager] updateAvailableProducts()") - - do { - let availableProducts = try await Product.products(for: Self.productIdentifiers) - print(" -- [PurchaseManager] updateAvailableProducts(): fetched \(availableProducts.count) products") - - if self.availableProducts != availableProducts { - print("availableProducts changed!") - self.availableProducts = availableProducts - } - } catch { - print("Error updating available products: \(error)") - } - } - - @MainActor - public func updatePurchasedProducts() async { - print(" -- [PurchaseManager] updatePurchasedProducts()") - - var purchasedSubscriptions: [String] = [] - - do { - for await result in Transaction.currentEntitlements { - let transaction = try checkVerified(result) - - guard transaction.productType == .autoRenewable else { continue } - guard transaction.revocationDate == nil else { continue } - - if let expirationDate = transaction.expirationDate, expirationDate > .now { - purchasedSubscriptions.append(transaction.productID) - - if let token = transaction.appAccountToken { - print(" -- [PurchaseManager] updatePurchasedProducts(): \(transaction.productID) -- custom UUID: \(token)" ) - } - } - } - } catch { - print("Error updating purchased products: \(error)") - } - - print(" -- [PurchaseManager] updatePurchasedProducts(): have \(purchasedSubscriptions.count) active subscriptions") - - if self.purchasedProductIDs != purchasedSubscriptions { - print("purchasedSubscriptions changed!") - self.purchasedProductIDs = purchasedSubscriptions - } - - subscriptionGroupStatus = try? await availableProducts.first?.subscription?.status.first?.state - } - - @MainActor - public static func mostRecentTransaction() async -> String? { - print(" -- [PurchaseManager] mostRecentTransaction()") - - var transactions: [VerificationResult] = [] - - for await result in Transaction.all { - transactions.append(result) - } - - print(" -- [PurchaseManager] mostRecentTransaction(): fetched \(transactions.count) transactions") - - return transactions.first?.jwsRepresentation - } - - @MainActor - public static func hasActiveSubscription() async -> Bool { - print(" -- [PurchaseManager] hasActiveSubscription()") - - var transactions: [VerificationResult] = [] - - for await result in Transaction.currentEntitlements { - transactions.append(result) - } - - print(" -- [PurchaseManager] hasActiveSubscription(): fetched \(transactions.count) transactions") - - return !transactions.isEmpty - } - - @MainActor - public func purchaseSubscription(with identifier: String, externalID: String) async -> Result { - - guard let product = availableProducts.first(where: { $0.id == identifier }) else { return .failure(PurchaseManagerError.productNotFound) } - - print(" -- [PurchaseManager] buy: \(product.displayName) (customUUID: \(externalID))") - - print("purchaseQueue append!") - purchaseQueue.append(product.id) - - print(" -- [PurchaseManager] starting purchase") - - var options: Set = Set() - - if let token = UUID(uuidString: externalID) { - options.insert(.appAccountToken(token)) - } else { - print("Wrong UUID") - return .failure(PurchaseManagerError.externalIDisNotAValidUUID) - } - - let result: Product.PurchaseResult - do { - result = try await product.purchase(options: options) - } catch { - print("error \(error)") - return .failure(PurchaseManagerError.purchaseFailed) - } - - print(" -- [PurchaseManager] purchase complete") - - purchaseQueue.removeAll() - print("purchaseQueue removeAll!") - - switch result { - case let .success(.verified(transaction)): - // Successful purchase - await transaction.finish() - await self.updatePurchasedProducts() - return .success(()) - case let .success(.unverified(_, error)): - // Successful purchase but transaction/receipt can't be verified - // Could be a jailbroken phone - print("Error: \(error.localizedDescription)") - return .failure(PurchaseManagerError.transactionCannotBeVerified) - case .pending: - // Transaction waiting on SCA (Strong Customer Authentication) or - // approval from Ask to Buy - return .failure(PurchaseManagerError.transactionPendingAuthentication) - case .userCancelled: - return .failure(PurchaseManagerError.purchaseCancelledByUser) - @unknown default: - return .failure(PurchaseManagerError.unknownError) - } - } - - private func checkVerified(_ result: VerificationResult) throws -> T { - // Check whether the JWS passes StoreKit verification. - switch result { - case .unverified: - // StoreKit parses the JWS, but it fails verification. - throw StoreError.failedVerification - case .verified(let safe): - // The result is verified. Return the unwrapped value. - return safe - } - } - - private func observeTransactionUpdates() -> Task { - - Task.detached { [unowned self] in - for await result in Transaction.updates { - print(" -- [PurchaseManager] observeTransactionUpdates()") - - if case .verified(let transaction) = result { - await transaction.finish() - } - - await self.updatePurchasedProducts() - } - } - } - - private func observeStorefrontChanges() -> Task { - - Task.detached { [unowned self] in - for await result in Storefront.updates { - print(" -- [PurchaseManager] observeStorefrontChanges(): \(result.countryCode)") - await updatePurchasedProducts() - await updateAvailableProducts() - } - } - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Services/APIService.swift b/DuckDuckGo/Subscription/Subscription/Services/APIService.swift deleted file mode 100644 index b0d036a41f..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Services/APIService.swift +++ /dev/null @@ -1,111 +0,0 @@ -// -// APIService.swift -// DuckDuckGo -// -// Copyright © 2023 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 Common - -public enum APIServiceError: Swift.Error { - case decodingError - case encodingError - case serverError(description: String) - case unknownServerError - case connectionError -} - -struct ErrorResponse: Decodable { - let error: String -} - -public protocol APIService { - static var baseURL: URL { get } - static var session: URLSession { get } - static func executeAPICall(method: String, endpoint: String, headers: [String: String]?, body: Data?) async -> Result where T: Decodable -} - -public extension APIService { - - static func executeAPICall(method: String, endpoint: String, headers: [String: String]? = nil, body: Data? = nil) async -> Result where T: Decodable { - let request = makeAPIRequest(method: method, endpoint: endpoint, headers: headers, body: body) - - do { - let (data, urlResponse) = try await session.data(for: request) - - printDebugInfo(method: method, endpoint: endpoint, data: data, response: urlResponse) - - if let httpResponse = urlResponse as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) { - if let decodedResponse = decode(T.self, from: data) { - return .success(decodedResponse) - } else { - return .failure(.decodingError) - } - } else { - if let decodedResponse = decode(ErrorResponse.self, from: data) { - let errorDescription = "[\(endpoint)] \(urlResponse.httpStatusCodeAsString ?? ""): \(decodedResponse.error)" - return .failure(.serverError(description: errorDescription)) - } else { - return .failure(.unknownServerError) - } - } - } catch { - os_log(.error, log: .subscription, "Service error: %{public}@", error.localizedDescription) - return .failure(.connectionError) - } - } - - private static func makeAPIRequest(method: String, endpoint: String, headers: [String: String]?, body: Data?) -> URLRequest { - let url = baseURL.appendingPathComponent(endpoint) - var request = URLRequest(url: url) - request.httpMethod = method - if let headers = headers { - request.allHTTPHeaderFields = headers - } - if let body = body { - request.httpBody = body - } - - return request - } - - private static func decode(_: T.Type, from data: Data) -> T? where T: Decodable { - let decoder = JSONDecoder() - decoder.keyDecodingStrategy = .convertFromSnakeCase - decoder.dateDecodingStrategy = .millisecondsSince1970 - - return try? decoder.decode(T.self, from: data) - } - - private static func printDebugInfo(method: String, endpoint: String, data: Data, response: URLResponse) { - let statusCode = (response as? HTTPURLResponse)!.statusCode - let stringData = String(data: data, encoding: .utf8) ?? "" - - os_log(.info, log: .subscription, "[API] %d %{public}s /%{public}s :: %{private}s", statusCode, method, endpoint, stringData) - } - - static func makeAuthorizationHeader(for token: String) -> [String: String] { - ["Authorization": "Bearer " + token] - } -} - -extension URLResponse { - - var httpStatusCodeAsString: String? { - guard let httpStatusCode = (self as? HTTPURLResponse)?.statusCode else { return nil } - return String(httpStatusCode) - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Services/AuthService.swift b/DuckDuckGo/Subscription/Subscription/Services/AuthService.swift deleted file mode 100644 index 1e4b7730ac..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Services/AuthService.swift +++ /dev/null @@ -1,117 +0,0 @@ -// -// AuthService.swift -// DuckDuckGo -// -// Copyright © 2023 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 Common - -public struct AuthService: APIService { - - public static let session = { - let configuration = URLSessionConfiguration.ephemeral - return URLSession(configuration: configuration) - }() - public static let baseURL = URL(string: "https://quackdev.duckduckgo.com/api/auth")! - - // MARK: - - - public static func getAccessToken(token: String) async -> Result { - await executeAPICall(method: "GET", endpoint: "access-token", headers: makeAuthorizationHeader(for: token)) - } - - public struct AccessTokenResponse: Decodable { - public let accessToken: String - } - - // MARK: - - - public static func validateToken(accessToken: String) async -> Result { - await executeAPICall(method: "GET", endpoint: "validate-token", headers: makeAuthorizationHeader(for: accessToken)) - } - - // swiftlint:disable nesting - public struct ValidateTokenResponse: Decodable { - public let account: Account - - public struct Account: Decodable { - public let email: String? - let entitlements: [Entitlement] - public let externalID: String - - enum CodingKeys: String, CodingKey { - case email, entitlements, externalID = "externalId" // no underscores due to keyDecodingStrategy = .convertFromSnakeCase - } - } - - struct Entitlement: Decodable { - let id: Int - let name: String - let product: String - } - } - // swiftlint:enable nesting - - // MARK: - - - public static func createAccount(emailAccessToken: String?) async -> Result { - var headers: [String: String]? - - if let emailAccessToken { - headers = makeAuthorizationHeader(for: emailAccessToken) - } - - return await executeAPICall(method: "POST", endpoint: "account/create", headers: headers) - } - - public struct CreateAccountResponse: Decodable { - public let authToken: String - public let externalID: String - public let status: String - - // swiftlint:disable nesting - enum CodingKeys: String, CodingKey { - case authToken = "authToken", externalID = "externalId", status // no underscores due to keyDecodingStrategy = .convertFromSnakeCase - } - // swiftlint:enable nesting - } - - // MARK: - - - public static func storeLogin(signature: String) async -> Result { - let bodyDict = ["signature": signature, - "store": "apple_app_store"] - - guard let bodyData = try? JSONEncoder().encode(bodyDict) else { return .failure(.encodingError) } - return await executeAPICall(method: "POST", endpoint: "store-login", body: bodyData) - } - - public struct StoreLoginResponse: Decodable { - public let authToken: String - public let email: String - public let externalID: String - public let id: Int - public let status: String - - // swiftlint:disable nesting - enum CodingKeys: String, CodingKey { - // no underscores due to keyDecodingStrategy = .convertFromSnakeCase - case authToken = "authToken", email, externalID = "externalId", id, status - } - // swiftlint:enable nesting - } -} diff --git a/DuckDuckGo/Subscription/Subscription/Services/SubscriptionService.swift b/DuckDuckGo/Subscription/Subscription/Services/SubscriptionService.swift deleted file mode 100644 index ff048cba70..0000000000 --- a/DuckDuckGo/Subscription/Subscription/Services/SubscriptionService.swift +++ /dev/null @@ -1,91 +0,0 @@ -// -// SubscriptionService.swift -// DuckDuckGo -// -// Copyright © 2023 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 Common - -public struct SubscriptionService: APIService { - - public static let session = { - let configuration = URLSessionConfiguration.ephemeral - return URLSession(configuration: configuration) - }() - public static let baseURL = URL(string: "https://subscriptions-dev.duckduckgo.com/api")! - - // MARK: - - - public static func getSubscriptionDetails(token: String) async -> Result { - let result: Result = await executeAPICall(method: "GET", - endpoint: "subscription", - headers: makeAuthorizationHeader(for: token)) - - switch result { - case .success(let response): - cachedSubscriptionDetailsResponse = response - case .failure: - cachedSubscriptionDetailsResponse = nil - } - - return result - } - - public struct GetSubscriptionDetailsResponse: Decodable { - public let productId: String - public let startedAt: Date - public let expiresOrRenewsAt: Date - public let platform: String - public let status: String - - public var isSubscriptionActive: Bool { - status.lowercased() != "expired" && status.lowercased() != "inactive" - } - } - - public static var cachedSubscriptionDetailsResponse: GetSubscriptionDetailsResponse? - - // MARK: - - - public static func getProducts() async -> Result { - await executeAPICall(method: "GET", endpoint: "products") - } - - public typealias GetProductsResponse = [GetProductsItem] - - public struct GetProductsItem: Decodable { - public let productId: String - public let productLabel: String - public let billingPeriod: String - public let price: String - public let currency: String - } - - // MARK: - - - public static func getCustomerPortalURL(accessToken: String, externalID: String) async -> Result { - var headers = makeAuthorizationHeader(for: accessToken) - headers["externalAccountId"] = externalID - return await executeAPICall(method: "GET", endpoint: "checkout/portal", headers: headers) - } - - public struct GetCustomerPortalURLResponse: Decodable { - public let customerPortalUrl: String - } - -} diff --git a/DuckDuckGo/Subscription/Subscription/SubscriptionPurchaseEnvironment.swift b/DuckDuckGo/Subscription/Subscription/SubscriptionPurchaseEnvironment.swift deleted file mode 100644 index f8847dafaf..0000000000 --- a/DuckDuckGo/Subscription/Subscription/SubscriptionPurchaseEnvironment.swift +++ /dev/null @@ -1,66 +0,0 @@ -// -// SubscriptionPurchaseEnvironment.swift -// DuckDuckGo -// -// Copyright © 2023 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 Common - -public final class SubscriptionPurchaseEnvironment { - - public enum Environment: String { - case appStore, stripe - } - - public static var current: Environment = .appStore { - didSet { - os_log(.info, log: .subscription, "[SubscriptionPurchaseEnvironment] Setting to %@", current.rawValue) - - canPurchase = false - - switch current { - case .appStore: - setupForAppStore() - case .stripe: - setupForStripe() - } - } - } - - public static var canPurchase: Bool = false { - didSet { - os_log(.info, log: .subscription, "[SubscriptionPurchaseEnvironment] canPurchase %@", (canPurchase ? "true" : "false")) - } - } - - private static func setupForAppStore() { - if #available(macOS 12.0, iOS 15.0, *) { - Task { - await PurchaseManager.shared.updateAvailableProducts() - canPurchase = !PurchaseManager.shared.availableProducts.isEmpty - } - } - } - - private static func setupForStripe() { - Task { - if case let .success(products) = await SubscriptionService.getProducts() { - canPurchase = !products.isEmpty - } - } - } -} diff --git a/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift b/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift deleted file mode 100644 index d8f35b7312..0000000000 --- a/DuckDuckGo/Subscription/Subscription/URL+Subscription.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// URL+Subscription.swift -// DuckDuckGo -// -// Copyright © 2023 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 - -public extension URL { - - static var purchaseSubscription: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/welcome")! - } - - static var subscriptionFAQ: URL { - URL(string: "https://duckduckgo.com/about")! - } - - // MARK: - Subscription Email - static var activateSubscriptionViaEmail: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/activate")! - } - - static var addEmailToSubscription: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/add-email")! - } - - static var manageSubscriptionEmail: URL { - URL(string: "https://abrown.duckduckgo.com/subscriptions/manage")! - } - - // MARK: - App Store app manage subscription URL - - static var manageSubscriptionsInAppStoreAppURL: URL { - URL(string: "macappstores://apps.apple.com/account/subscriptions")! - } - - static var manageSubscriptionsIniOSAppStoreAppURL: URL { - URL(string: "https://apps.apple.com/account/subscriptions")! - } - - // MARK: - Identity Theft Protection - static var manageITP: URL { - URL(string: "https://abrown.duckduckgo.com/identity-theft-restoration")! - } -} diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 28aa9881f0..b094c71124 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -24,6 +24,7 @@ import Foundation import WebKit import UserScript import Combine +import Subscription @available(iOS 15.0, *) final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 780b317fbe..90b2eacc49 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -24,6 +24,7 @@ import Foundation import WebKit import UserScript import Combine +import Subscription @available(iOS 15.0, *) final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObject { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index abe47f3fc5..0a492e5e25 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -23,6 +23,7 @@ import Combine import Core #if SUBSCRIPTION +import Subscription @available(iOS 15.0, *) final class SubscriptionEmailViewModel: ObservableObject { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 084717c1b3..d42dbb0e61 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -22,7 +22,9 @@ import UserScript import Combine import Core + #if SUBSCRIPTION +import Subscription @available(iOS 15.0, *) final class SubscriptionFlowViewModel: ObservableObject { @@ -39,7 +41,7 @@ final class SubscriptionFlowViewModel: ObservableObject { private var canGoBackCancellable: AnyCancellable? // State variables - var purchaseURL = URL.purchaseSubscription + var purchaseURL = URL.subscriptionPurchase enum FeatureName { static let netP = "vpn" diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index a076f08341..d189f1a6f1 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -23,12 +23,13 @@ import Combine import Core #if SUBSCRIPTION +import Subscription @available(iOS 15.0, *) final class SubscriptionITPViewModel: ObservableObject { let userScript: IdentityTheftRestorationPagesUserScript let subFeature: IdentityTheftRestorationPagesFeature - var manageITPURL = URL.manageITP + var manageITPURL = URL.identityTheftRestoration var viewTitle = UserText.settingsPProITRTitle enum Constants { @@ -37,7 +38,7 @@ final class SubscriptionITPViewModel: ObservableObject { } // State variables - var itpURL = URL.manageITP + var itpURL = URL.identityTheftRestoration @Published var webViewModel: AsyncHeadlessWebViewViewModel @Published var shouldShowNavigationBar: Bool = false @Published var canNavigateBack: Bool = false diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 0733cd2e83..666c6fe77e 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -23,6 +23,7 @@ import Combine import Core #if SUBSCRIPTION +import Subscription @available(iOS 15.0, *) final class SubscriptionRestoreViewModel: ObservableObject { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 4cf960c44f..3619b69b95 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -22,6 +22,7 @@ import SwiftUI import StoreKit #if SUBSCRIPTION +import Subscription @available(iOS 15.0, *) final class SubscriptionSettingsViewModel: ObservableObject { @@ -81,7 +82,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { } private func openSubscriptionManagementURL() { - let url = URL.manageSubscriptionsIniOSAppStoreAppURL + let url = URL.manageSubscriptionsInAppStoreAppURL if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:], completionHandler: nil) } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index d688ebf0e1..8e571649f7 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -19,6 +19,7 @@ import UIKit + #if !SUBSCRIPTION final class SubscriptionDebugViewController: UITableViewController { @@ -26,6 +27,7 @@ final class SubscriptionDebugViewController: UITableViewController { } #else +import Subscription @available(iOS 15.0, *) final class SubscriptionDebugViewController: UITableViewController { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 9765ab738d..17a9dfc50e 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/.gitignore b/LocalPackages/Subscription/.gitignore deleted file mode 100644 index 0023a53406..0000000000 --- a/LocalPackages/Subscription/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -.DS_Store -/.build -/Packages -xcuserdata/ -DerivedData/ -.swiftpm/configuration/registries.json -.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata -.netrc diff --git a/LocalPackages/Subscription/Package.resolved b/LocalPackages/Subscription/Package.resolved deleted file mode 100644 index c4a8bbc9a0..0000000000 --- a/LocalPackages/Subscription/Package.resolved +++ /dev/null @@ -1,104 +0,0 @@ -{ - "pins" : [ - { - "identity" : "bloom_cpp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/bloom_cpp.git", - "state" : { - "revision" : "8076199456290b61b4544bf2f4caf296759906a0", - "version" : "3.0.0" - } - }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", - "state" : { - "revision" : "4cf8e857cd78e15c64ba37839634970fc675947c", - "version" : "81.4.0" - } - }, - { - "identity" : "content-scope-scripts", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/content-scope-scripts", - "state" : { - "revision" : "aa279a3b006a0b1e009707311283c7fcaed24fb7", - "version" : "4.39.0" - } - }, - { - "identity" : "duckduckgo-autofill", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", - "state" : { - "revision" : "6dd7d696d4e666cedb2f1890a46fe53615226646", - "version" : "8.4.2" - } - }, - { - "identity" : "grdb.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/GRDB.swift.git", - "state" : { - "revision" : "77d9a83191a74e319a5cfa27b0e3145d15607573", - "version" : "2.2.0" - } - }, - { - "identity" : "privacy-dashboard", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/privacy-dashboard", - "state" : { - "revision" : "51e2b46f413bf3ef18afefad631ca70f2c25ef70", - "version" : "1.4.0" - } - }, - { - "identity" : "punycodeswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gumob/PunycodeSwift.git", - "state" : { - "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b", - "version" : "2.1.0" - } - }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser", - "state" : { - "revision" : "6b2aa2748a7881eebb9f84fb10c01293e15b52ca", - "version" : "0.5.0" - } - }, - { - "identity" : "sync_crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/sync_crypto", - "state" : { - "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", - "version" : "0.2.0" - } - }, - { - "identity" : "trackerradarkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", - "state" : { - "revision" : "4684440d03304e7638a2c8086895367e90987463", - "version" : "1.2.1" - } - }, - { - "identity" : "wireguard-apple", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/wireguard-apple", - "state" : { - "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", - "version" : "1.1.1" - } - } - ], - "version" : 2 -} diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift deleted file mode 100644 index c65b6109c7..0000000000 --- a/LocalPackages/Subscription/Package.swift +++ /dev/null @@ -1,27 +0,0 @@ -// swift-tools-version: 5.9 -// The swift-tools-version declares the minimum version of Swift required to build this package. - -import PackageDescription - -let package = Package( - name: "Subscription", - platforms: [ .macOS(.v11), .iOS(.v14) ], - products: [ - .library( - name: "Subscription", - targets: ["Subscription"]), - ], - dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "94.0.0"), - ], - targets: [ - .target( - name: "Subscription", - dependencies: [ - .product(name: "BrowserServicesKit", package: "BrowserServicesKit"), - ]), - .testTarget( - name: "SubscriptionTests", - dependencies: ["Subscription"]), - ] -) diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index aa8c6af19b..32b5b7b4e1 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 2d0a802dba..9496370013 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "106.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 1f2ff2e1fbe82c5a709b2c94fa7808bf997b717b Mon Sep 17 00:00:00 2001 From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:35:41 +0100 Subject: [PATCH 031/245] fix sync tests (#2476) Task/Issue URL: https://app.asana.com/0/414709148257752/1206588762882670/f Description: Fix sync tests after introduction of authentication --- .maestro/shared/sync_create.yaml | 2 ++ .maestro/shared/sync_login.yaml | 2 ++ .maestro/shared/sync_recover_data.yaml | 2 ++ .maestro/sync_tests/05_delete_account.yaml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/.maestro/shared/sync_create.yaml b/.maestro/shared/sync_create.yaml index f7be860848..5ebbbed116 100644 --- a/.maestro/shared/sync_create.yaml +++ b/.maestro/shared/sync_create.yaml @@ -4,6 +4,8 @@ appId: com.duckduckgo.mobile.ios - tapOn: Sync & Backup - assertVisible: Sync & Backup - tapOn: Sync and Back Up This Device +- inputText: "0000" +- pressKey: Enter - assertVisible: You can sync with your other devices later. - tapOn: Turn On Sync & Back Up - assertVisible: Save Recovery Code diff --git a/.maestro/shared/sync_login.yaml b/.maestro/shared/sync_login.yaml index e63fb885ef..5f52e15c9a 100644 --- a/.maestro/shared/sync_login.yaml +++ b/.maestro/shared/sync_login.yaml @@ -3,6 +3,8 @@ appId: com.duckduckgo.mobile.ios - assertVisible: Begin Syncing - tapOn: Sync with Another Device +- inputText: "0000" +- pressKey: Enter - assertVisible: Scan QR Code - tapOn: Manually Enter Code - tapOn: Paste diff --git a/.maestro/shared/sync_recover_data.yaml b/.maestro/shared/sync_recover_data.yaml index 8c81e717c1..f6ce10fcec 100644 --- a/.maestro/shared/sync_recover_data.yaml +++ b/.maestro/shared/sync_recover_data.yaml @@ -6,6 +6,8 @@ appId: com.duckduckgo.mobile.ios - assertVisible: Begin Syncing - tapOn: Recover Synced Data - assertVisible: Recover Synced Data +- inputText: "0000" +- pressKey: Enter - assertVisible: Get Started - tapOn: Get Started - tapOn: Enter Text Code Manually diff --git a/.maestro/sync_tests/05_delete_account.yaml b/.maestro/sync_tests/05_delete_account.yaml index 1a40d0916a..541a8f9174 100644 --- a/.maestro/sync_tests/05_delete_account.yaml +++ b/.maestro/sync_tests/05_delete_account.yaml @@ -32,6 +32,8 @@ tags: # Try to login and check for error - assertVisible: Begin Syncing - tapOn: Sync with Another Device +- inputText: "0000" +- pressKey: Enter - assertVisible: Scan QR Code - tapOn: Manually Enter Code - tapOn: Paste From d9a3568e871476e48c3642aec87a280eb375952c Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 15 Feb 2024 15:49:40 +0100 Subject: [PATCH 032/245] Updates BSK (#2448) Task/Issue URL: https://app.asana.com/0/1203137811378537/1206513608690551/f Tech Design URL: CC: BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/655 macOS PR: https://github.com/duckduckgo/macos-browser/pull/2174 ## Description Just integrating the latest macOS BSK changes. There should literally be no changes for iOS, but please double check. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- ...etworkProtectionConvenienceInitialisers.swift | 16 +++++++++++++--- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 6 files changed, 19 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4902911bc7..e5c8134a66 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9892,7 +9892,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 107.0.0; + version = 107.0.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ae8e29e12e..b1a147e636 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "328ce451fd1593809d1470ab5a0b5242a595f88c", - "version" : "107.0.0" + "revision" : "93a9a4153429de38c8b9f186617ff34db84f9250", + "version" : "107.0.1" } }, { diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 8698a6df68..01d9287bd7 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -22,24 +22,34 @@ import NetworkProtection import UIKit import Common +import NetworkExtension + +private class DefaultTunnelSessionProvider: TunnelSessionProvider { + func activeSession() async -> NETunnelProviderSession? { + try? await ConnectionSessionUtilities.activeSession() + } +} extension ConnectionStatusObserverThroughSession { convenience init() { - self.init(platformNotificationCenter: .default, + self.init(tunnelSessionProvider: DefaultTunnelSessionProvider(), + platformNotificationCenter: .default, platformDidWakeNotification: UIApplication.didBecomeActiveNotification) } } extension ConnectionErrorObserverThroughSession { convenience init() { - self.init(platformNotificationCenter: .default, + self.init(tunnelSessionProvider: DefaultTunnelSessionProvider(), + platformNotificationCenter: .default, platformDidWakeNotification: UIApplication.didBecomeActiveNotification) } } extension ConnectionServerInfoObserverThroughSession { convenience init() { - self.init(platformNotificationCenter: .default, + self.init(tunnelSessionProvider: DefaultTunnelSessionProvider(), + platformNotificationCenter: .default, platformDidWakeNotification: UIApplication.didBecomeActiveNotification) } } diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 17a9dfc50e..168afca2ae 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 32b5b7b4e1..efff9ba53e 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 9496370013..1984628127 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 6971f7533ad992fe63a40c9569771be9b2e058d2 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Sun, 18 Feb 2024 11:27:05 -0800 Subject: [PATCH 033/245] Improve VPN rekeying reliability (#2478) Task/Issue URL: https://app.asana.com/0/414235014887631/1206607513978260/f Tech Design URL: CC: Description: Client PR for duckduckgo/BrowserServicesKit#664 --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e5c8134a66..4291c4d13c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9892,7 +9892,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 107.0.1; + version = 108.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b1a147e636..c289646e01 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "93a9a4153429de38c8b9f186617ff34db84f9250", - "version" : "107.0.1" + "revision" : "39a0ed6853b823a33d85277ca3ee4385f81e4595", + "version" : "108.0.0" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 168afca2ae..725ddf1a56 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index efff9ba53e..46c92cd127 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 1984628127..14138025e3 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "107.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From cf731d42b8b211a7b484a63fdfbb33dd5f985037 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 19 Feb 2024 11:05:29 +0100 Subject: [PATCH 034/245] Add debug menu option to enable Autofill debug script (#2475) Task/Issue URL: https://app.asana.com/0/1203822806345703/1206298121584969/f Tech Design URL: CC: Description: Add control of Autofill JS debug script via UI debug menu --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppUserDefaults.swift | 14 +++ DuckDuckGo/AutofillDebugViewController.swift | 54 ++++++++++++ DuckDuckGo/ContentBlockingUpdating.swift | 1 + DuckDuckGo/Debug.storyboard | 88 +++++++++++++++---- DuckDuckGo/RootDebugViewController.swift | 6 -- DuckDuckGo/ScriptSourceProviding.swift | 3 +- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 11 files changed, 150 insertions(+), 32 deletions(-) create mode 100644 DuckDuckGo/AutofillDebugViewController.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4291c4d13c..c796e82d7a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -707,6 +707,7 @@ C14882E827F20DAB00D59F0C /* TestDataLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882E627F20DAB00D59F0C /* TestDataLoader.swift */; }; C14882EA27F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882E927F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift */; }; C14882ED27F211A000D59F0C /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = C14882EC27F211A000D59F0C /* SwiftSoup */; }; + C14D43012B45D6CD00ACA4DC /* AutofillDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */; }; C14E2F7729DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14E2F7629DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift */; }; C158AC7B297AB5DC0008723A /* MockSecureVault.swift in Sources */ = {isa = PBXBuildFile; fileRef = C158AC7A297AB5DC0008723A /* MockSecureVault.swift */; }; C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159DF062A430B60007834BB /* EmailSignupViewController.swift */; }; @@ -2338,6 +2339,7 @@ C14882E527F20DAA00D59F0C /* HtmlTestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HtmlTestDataLoader.swift; sourceTree = ""; }; C14882E627F20DAB00D59F0C /* TestDataLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestDataLoader.swift; sourceTree = ""; }; C14882E927F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockBookmarksCoreDataStorage.swift; sourceTree = ""; }; + C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillDebugViewController.swift; sourceTree = ""; }; C14E2F7629DE14EA002AC515 /* AutofillInterfaceUsernameTruncatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillInterfaceUsernameTruncatorTests.swift; sourceTree = ""; }; C158AC7A297AB5DC0008723A /* MockSecureVault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockSecureVault.swift; sourceTree = ""; }; C159DF062A430B60007834BB /* EmailSignupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupViewController.swift; sourceTree = ""; }; @@ -3867,6 +3869,7 @@ 858566F1252E55AE007501B8 /* Debug */ = { isa = PBXGroup; children = ( + C14D43002B45D6CD00ACA4DC /* AutofillDebugViewController.swift */, 858566E7252E4F56007501B8 /* Debug.storyboard */, D6F93E3B2B4FFA97004C268D /* SubscriptionDebugViewController.swift */, 8590CB602684D0600089F6BF /* CookieDebugViewController.swift */, @@ -6498,6 +6501,7 @@ D6E83C5E2B224676006C8AFB /* SettingsCustomizeView.swift in Sources */, 1E8AD1C727BE9B2900ABA377 /* DownloadsListDataSource.swift in Sources */, 3157B43527F497F50042D3D7 /* SaveLoginViewController.swift in Sources */, + C14D43012B45D6CD00ACA4DC /* AutofillDebugViewController.swift in Sources */, 853C5F6121C277C7001F7A05 /* global.swift in Sources */, EE9D68D82AE15AD600B55EF4 /* UIApplicationExtension.swift in Sources */, F13B4BD31F1822C700814661 /* Tab.swift in Sources */, @@ -9892,7 +9896,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 108.0.0; + version = 108.1.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c289646e01..4db92a9200 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "39a0ed6853b823a33d85277ca3ee4385f81e4595", - "version" : "108.0.0" + "revision" : "ab03bde3e1817b267debe9858a08b3f0caf72dc3", + "version" : "108.1.0" } }, { diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 008facdf6a..446bd75fc3 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -22,6 +22,7 @@ import Bookmarks import Core import WidgetKit +// swiftlint:disable file_length public class AppUserDefaults: AppSettings { public struct Notifications { @@ -35,6 +36,7 @@ public class AppUserDefaults: AppSettings { public static let didVerifyInternalUser = Notification.Name("com.duckduckgo.app.DidVerifyInternalUser") public static let inspectableWebViewsToggled = Notification.Name("com.duckduckgo.app.DidToggleInspectableWebViews") public static let addressBarPositionChanged = Notification.Name("com.duckduckgo.app.AddressBarPositionChanged") + public static let autofillDebugScriptToggled = Notification.Name("com.duckduckgo.app.DidToggleAutofillDebugScript") } private let groupName: String @@ -72,6 +74,7 @@ public class AppUserDefaults: AppSettings { private struct DebugKeys { static let inspectableWebViewsEnabledKey = "com.duckduckgo.ios.debug.inspectableWebViewsEnabled" + static let autofillDebugScriptEnabledKey = "com.duckduckgo.ios.debug.autofillDebugScriptEnabled" } private var userDefaults: UserDefaults? { @@ -321,6 +324,17 @@ public class AppUserDefaults: AppSettings { userDefaults?.set(newValue, forKey: DebugKeys.inspectableWebViewsEnabledKey) } } + + var autofillDebugScriptEnabled: Bool { + get { + return userDefaults?.object(forKey: DebugKeys.autofillDebugScriptEnabledKey) as? Bool ?? false + } + + set { + userDefaults?.set(newValue, forKey: DebugKeys.autofillDebugScriptEnabledKey) + } + } + } extension AppUserDefaults: AppConfigurationFetchStatistics { diff --git a/DuckDuckGo/AutofillDebugViewController.swift b/DuckDuckGo/AutofillDebugViewController.swift new file mode 100644 index 0000000000..c57819bac2 --- /dev/null +++ b/DuckDuckGo/AutofillDebugViewController.swift @@ -0,0 +1,54 @@ +// +// AutofillDebugViewController.swift +// DuckDuckGo +// +// Copyright © 2023 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 UIKit +import BrowserServicesKit + +class AutofillDebugViewController: UITableViewController { + + enum Row: Int { + case toggleAutofillDebugScript = 201 + case resetEmailProtectionInContextSignUp = 202 + } + + let defaults = AppUserDefaults() + + override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + if cell.tag == Row.toggleAutofillDebugScript.rawValue { + cell.accessoryType = defaults.autofillDebugScriptEnabled ? .checkmark : .none + } + } + + override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { + tableView.deselectRow(at: indexPath, animated: true) + + if let cell = tableView.cellForRow(at: indexPath) { + if cell.tag == Row.toggleAutofillDebugScript.rawValue { + defaults.autofillDebugScriptEnabled.toggle() + cell.accessoryType = defaults.autofillDebugScriptEnabled ? .checkmark : .none + NotificationCenter.default.post(Notification(name: AppUserDefaults.Notifications.autofillDebugScriptToggled)) + } else if cell.tag == Row.resetEmailProtectionInContextSignUp.rawValue { + EmailManager().resetEmailProtectionInContextPrompt() + tableView.deselectRow(at: indexPath, animated: true) + } + } + + } + +} diff --git a/DuckDuckGo/ContentBlockingUpdating.swift b/DuckDuckGo/ContentBlockingUpdating.swift index ef1fb29629..95e70dbcf6 100644 --- a/DuckDuckGo/ContentBlockingUpdating.swift +++ b/DuckDuckGo/ContentBlockingUpdating.swift @@ -91,6 +91,7 @@ public final class ContentBlockingUpdating { .combineLatest(onNotificationWithInitial(AppUserDefaults.Notifications.didVerifyInternalUser), combine) .combineLatest(onNotificationWithInitial(ConfigurationManager.didUpdateTrackerDependencies) .receive(on: DispatchQueue.main), combine) + .combineLatest(onNotificationWithInitial(AppUserDefaults.Notifications.autofillDebugScriptToggled), combine) // DefaultScriptSourceProvider instance should be created once per rules/config change and fed into UserScripts initialization .map(makeValue) .assign(to: \.bufferedValue, onWeaklyHeld: self) // buffer latest update value diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index ecddf0d91a..7443344405 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -101,10 +101,10 @@ - + - + @@ -221,23 +221,26 @@ - + - - + + - + + + + - + - + - + @@ -298,6 +301,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -796,34 +846,34 @@ - + - + - + - + @@ -875,13 +925,13 @@ - + - + diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 485c269970..d54d1dec05 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -36,7 +36,6 @@ class RootDebugViewController: UITableViewController { case crashMemory = 667 case toggleInspectableWebViews = 668 case toggleInternalUserState = 669 - case resetEmailProtectionInContextSignUp = 670 } @IBOutlet weak var shareButton: UIBarButtonItem! @@ -143,11 +142,6 @@ class RootDebugViewController: UITableViewController { NotificationCenter.default.post(Notification(name: AppUserDefaults.Notifications.inspectableWebViewsToggled)) } - if tableView.cellForRow(at: indexPath)?.tag == Row.resetEmailProtectionInContextSignUp.rawValue { - EmailManager().resetEmailProtectionInContextPrompt() - tableView.deselectRow(at: indexPath, animated: true) - } - } } diff --git a/DuckDuckGo/ScriptSourceProviding.swift b/DuckDuckGo/ScriptSourceProviding.swift index 615dfc71a2..99ed069329 100644 --- a/DuckDuckGo/ScriptSourceProviding.swift +++ b/DuckDuckGo/ScriptSourceProviding.swift @@ -75,7 +75,8 @@ struct DefaultScriptSourceProvider: ScriptSourceProviding { private static func makeAutofillSource(privacyConfigurationManager: PrivacyConfigurationManaging, properties: ContentScopeProperties) -> AutofillUserScriptSourceProvider { return DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: privacyConfigurationManager, - properties: properties) + properties: properties, + isDebug: AppUserDefaults().autofillDebugScriptEnabled) .withJSLoading() .build() } diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 725ddf1a56..c74e599728 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.1.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 46c92cd127..7032a49924 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.1.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 14138025e3..9a9e0358d0 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.1.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From f9e53db4cc1753a5cc4ec3acc9b688327e096b83 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 19 Feb 2024 11:04:56 +0000 Subject: [PATCH 035/245] fix widgets on iOS 16 (#2482) --- Widgets/WidgetViews.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Widgets/WidgetViews.swift b/Widgets/WidgetViews.swift index 034d189ceb..60e6902090 100644 --- a/Widgets/WidgetViews.swift +++ b/Widgets/WidgetViews.swift @@ -252,6 +252,8 @@ extension View { containerBackground(for: .widget) { color } + } else { + self } } From 578352dd4dc19b20f9443c8c8475607c44d90458 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 19 Feb 2024 12:33:37 +0100 Subject: [PATCH 036/245] Send MM message in case of App Store Release failure (#2466) --- .github/workflows/release.yml | 8 ++++---- scripts/assets/appstore-release-mm-template.json | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 49b135d79f..f61b8358d8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -108,7 +108,8 @@ jobs: --form "file=@${asana_dsyms_path};type=application/zip" fi - - name: Send Mattermost message + - name: Send Mattermost message + if: ${{ success() || failure() }} # Don't execute when cancelled env: WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} DESTINATION: ${{ steps.destination.outputs.destination }} @@ -117,9 +118,8 @@ jobs: if [[ -z "${MM_USER_HANDLE}" ]]; then echo "Mattermost user handle not known for ${{ github.actor }}, skipping sending message" - else - + else curl -s -H 'Content-type: application/json' \ - -d "$(envsubst < ./scripts/assets/appstore-release-mm-template.json)" \ + -d "$(envsubst < ./scripts/assets/appstore-release-mm-template.json | jq ".${{ job.status }}")" \ ${{ secrets.MM_WEBHOOK_URL }} fi diff --git a/scripts/assets/appstore-release-mm-template.json b/scripts/assets/appstore-release-mm-template.json index eb4d1cab41..54c59b9f28 100644 --- a/scripts/assets/appstore-release-mm-template.json +++ b/scripts/assets/appstore-release-mm-template.json @@ -1 +1,14 @@ -{"channel":"${MM_USER_HANDLE}","username":"GitHub Actions","text":"**iOS app has been successfully uploaded to ${DESTINATION}** :goose_honk_tada: | [:github: Workflow run summary](${WORKFLOW_URL})","icon_url":"https://duckduckgo.com/assets/logo_header.v108.svg"} +{ + "failure": { + "channel": "${MM_USER_HANDLE}", + "username": "GitHub Actions", + "text": ":warning: **iOS release job failed** :thisisfine: | [:github: Workflow run summary](${WORKFLOW_URL})", + "icon_url": "https://duckduckgo.com/assets/logo_header.v108.svg" + }, + "success": { + "channel": "${MM_USER_HANDLE}", + "username": "GitHub Actions", + "text": "**iOS app has been successfully uploaded to ${DESTINATION}** :goose_honk_tada: | [:github: Workflow run summary](${WORKFLOW_URL})", + "icon_url": "https://duckduckgo.com/assets/logo_header.v108.svg" + } +} \ No newline at end of file From dad7d3371362ca505a781b550399971dff232aef Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 19 Feb 2024 13:56:07 +0000 Subject: [PATCH 037/245] clean up clear data code (#2449) --- .../data_clearing_tests/01_fire_proofing.yml | 75 ++++ .../02_duckduckgo_settings.yml | 44 ++ Core/CookieStorage.swift | 23 +- Core/DataStoreWarmup.swift | 72 +++ Core/PixelEvent.swift | 6 +- Core/PreserveLogins.swift | 1 - Core/WebCacheManager.swift | 420 ++++-------------- DuckDuckGo.xcodeproj/project.pbxproj | 4 + DuckDuckGo/AppDelegate.swift | 35 +- DuckDuckGo/AutoClear.swift | 26 +- DuckDuckGo/CookieDebugViewController.swift | 6 +- DuckDuckGo/FireButtonAnimator.swift | 32 +- DuckDuckGo/MainViewController.swift | 66 +-- ...PreserveLoginsSettingsViewController.swift | 17 +- DuckDuckGo/RootDebugViewController.swift | 2 +- DuckDuckGo/TabManager.swift | 9 - DuckDuckGo/TabViewController.swift | 15 +- DuckDuckGoTests/AutoClearTests.swift | 61 +-- DuckDuckGoTests/CookieStorageTests.swift | 60 ++- DuckDuckGoTests/DownloadManagerTests.swift | 16 +- .../FireButtonReferenceTests.swift | 34 +- DuckDuckGoTests/WebCacheManagerTests.swift | 135 ++---- 22 files changed, 547 insertions(+), 612 deletions(-) create mode 100644 .maestro/data_clearing_tests/01_fire_proofing.yml create mode 100644 .maestro/data_clearing_tests/02_duckduckgo_settings.yml create mode 100644 Core/DataStoreWarmup.swift diff --git a/.maestro/data_clearing_tests/01_fire_proofing.yml b/.maestro/data_clearing_tests/01_fire_proofing.yml new file mode 100644 index 0000000000..e717392439 --- /dev/null +++ b/.maestro/data_clearing_tests/01_fire_proofing.yml @@ -0,0 +1,75 @@ +appId: com.duckduckgo.mobile.ios +tags: + - dataclearing + +--- + +# Set up +- clearState +- launchApp +- runFlow: + when: + visible: + text: "Let’s Do It!" + index: 0 + file: ../shared/onboarding.yaml + +# Load Site +- assertVisible: + id: "searchEntry" +- tapOn: + id: "searchEntry" +- inputText: "https://setcookie.net" +- pressKey: Enter + +# Set a cookie +- assertVisible: "Cookie Test" +- tapOn: "Cookie name" +- inputText: "TestName" +- tapOn: "Cookie value" +- inputText: "TestValue" +- scrollUntilVisible: + centerElement: true + element: + text: "Submit" +- tapOn: "Submit" + +# Fireproof the site +- tapOn: "Browsing Menu" +- tapOn: "Fireproof This Site" +- tapOn: "Fireproof" +- assertVisible: "setcookie.net is now Fireproof" + +# Fire Button - twice, just to be sure +- tapOn: "Close Tabs and Clear Data" +- tapOn: + id: "alert.forget-data.confirm" +- assertVisible: + id: "searchEntry" +- tapOn: "Close Tabs and Clear Data" +- tapOn: + id: "alert.forget-data.confirm" + +# Validate Cookie was retained +- tapOn: + id: "searchEntry" +- inputText: "https://setcookie.net" +- pressKey: Enter +- assertVisible: "TestName = TestValue" + +# Remove fireproofing +- tapOn: "Browsing Menu" +- tapOn: "Remove Fireproofing" + +# Fire Button +- tapOn: "Close Tabs and Clear Data" +- tapOn: + id: "alert.forget-data.confirm" + +# Validate Cookie was removed +- tapOn: + id: "searchEntry" +- inputText: "https://setcookie.net" +- pressKey: Enter +- assertVisible: "Cookie Test" +- assertVisible: "Received no cookies." diff --git a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml new file mode 100644 index 0000000000..9de815dc35 --- /dev/null +++ b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml @@ -0,0 +1,44 @@ +appId: com.duckduckgo.mobile.ios +tags: + - privacy + +--- + +# Set up +- clearState +- launchApp +- runFlow: + when: + visible: + text: "Let’s Do It!" + index: 0 + file: ../shared/onboarding.yaml + +# Load Site +- assertVisible: + id: "searchEntry" +- tapOn: + id: "searchEntry" +- inputText: "privacy blogs" +- pressKey: Enter + +# Change settings +- tapOn: "Safe search: moderate ▼" +- tapOn: "Off" + +# Fire Button - twice, just to be sure +- tapOn: "Close Tabs and Clear Data" +- tapOn: + id: "alert.forget-data.confirm" +- assertVisible: + id: "searchEntry" +- tapOn: "Close Tabs and Clear Data" +- tapOn: + id: "alert.forget-data.confirm" + +# Validate Cookie was retained +- tapOn: + id: "searchEntry" +- inputText: "creepy trackers" +- pressKey: Enter +- assertVisible: "Safe search: off ▼" diff --git a/Core/CookieStorage.swift b/Core/CookieStorage.swift index 0a83d6cba1..0349d9841a 100644 --- a/Core/CookieStorage.swift +++ b/Core/CookieStorage.swift @@ -20,6 +20,12 @@ import Common import Foundation +/// Class for persisting cookies for fire proofed sites to work around a WKWebView / DataStore bug which does not let data get persisted until the webview has loaded. +/// +/// Privacy information: +/// * The Fire Button does not delete the user's DuckDuckGo search settings, which are saved as cookies. Removing these cookies would reset them and have undesired consequences, i.e. changing the theme, default language, etc. +/// * The Fire Button also does not delete temporary cookies associated with 'surveys.duckduckgo.com'. When we launch surveys to help us understand issues that impact users over time, we use this cookie to temporarily store anonymous survey answers, before deleting the cookie. Cookie storage duration is communicated to users before they opt to submit survey answers. +/// * These cookies are not stored in a personally identifiable way. For example, the large size setting is stored as 's=l.' More info in https://duckduckgo.com/privacy public class CookieStorage { struct Keys { @@ -31,7 +37,7 @@ public class CookieStorage { var isConsumed: Bool { get { - userDefaults.bool(forKey: Keys.consumed, defaultValue: false) + return userDefaults.bool(forKey: Keys.consumed, defaultValue: false) } set { userDefaults.set(newValue, forKey: Keys.consumed) @@ -77,15 +83,20 @@ public class CookieStorage { self.userDefaults = userDefaults } - enum CookieDomainsOnUpdate { + /// Used when debugging (e.g. on the simulator). + enum CookieDomainsOnUpdateDiagnostic { case empty case match case missing case different + case notConsumed } + /// Update ALL cookies. The absence of cookie domains here indicateds they have been removed by the website, so be sure to call this with all cookies that might need to be persisted even if those websites have not been visited yet. @discardableResult - func updateCookies(_ cookies: [HTTPCookie], keepingPreservedLogins preservedLogins: PreserveLogins) -> CookieDomainsOnUpdate { + func updateCookies(_ cookies: [HTTPCookie], keepingPreservedLogins preservedLogins: PreserveLogins) -> CookieDomainsOnUpdateDiagnostic { + guard isConsumed else { return .notConsumed } + isConsumed = false let persisted = self.cookies @@ -109,7 +120,9 @@ public class CookieStorage { persistedDomains: persistedCookiesByDomain.keys.sorted() ) - updatedCookiesByDomain.keys.forEach { + let cookieDomains = Set(updatedCookiesByDomain.keys.map { $0 } + persistedCookiesByDomain.keys.map { $0 }) + + cookieDomains.forEach { persistedCookiesByDomain[$0] = updatedCookiesByDomain[$0] } @@ -128,7 +141,7 @@ public class CookieStorage { return diagnosticResult } - private func evaluateDomains(updatedDomains: [String], persistedDomains: [String]) -> CookieDomainsOnUpdate { + private func evaluateDomains(updatedDomains: [String], persistedDomains: [String]) -> CookieDomainsOnUpdateDiagnostic { if persistedDomains.isEmpty { return .empty } else if updatedDomains.count < persistedDomains.count { diff --git a/Core/DataStoreWarmup.swift b/Core/DataStoreWarmup.swift new file mode 100644 index 0000000000..79087b9fc2 --- /dev/null +++ b/Core/DataStoreWarmup.swift @@ -0,0 +1,72 @@ +// +// DataStoreWarmup.swift +// DuckDuckGo +// +// 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 Combine +import WebKit + +/// WKWebsiteDataStore is basically non-functional until a web view has been instanciated and a page is successfully loaded. +public class DataStoreWarmup { + + public init() { } + + @MainActor + public func ensureReady() async { + await BlockingNavigationDelegate().loadInBackgroundWebView(url: URL(string: "about:blank")!) + } + +} + +private class BlockingNavigationDelegate: NSObject, WKNavigationDelegate { + + let finished = PassthroughSubject() + + func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { + return .allow + } + + func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + finished.send() + } + + func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { + Pixel.fire(pixel: .webKitDidTerminateDuringWarmup) + // We won't get a `didFinish` if the webview crashes + finished.send() + } + + var cancellable: AnyCancellable? + func waitForLoad() async { + await withCheckedContinuation { continuation in + cancellable = finished.sink { _ in + continuation.resume() + } + } + } + + @MainActor + func loadInBackgroundWebView(url: URL) async { + let config = WKWebViewConfiguration.persistent() + let webView = WKWebView(frame: .zero, configuration: config) + webView.navigationDelegate = self + let request = URLRequest(url: url) + webView.load(request) + await waitForLoad() + } + +} diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 49166d4662..7261d5bafa 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -394,7 +394,8 @@ extension Pixel { case webKitDidTerminate case webKitTerminationDidReloadCurrentTab - + case webKitDidTerminateDuringWarmup + case backgroundTaskSubmissionFailed case blankOverlayNotDismissed @@ -891,6 +892,7 @@ extension Pixel.Event { case .ampBlockingRulesCompilationFailed: return "m_debug_amp_rules_compilation_failed" case .webKitDidTerminate: return "m_d_wkt" + case .webKitDidTerminateDuringWarmup: return "m_d_webkit-terminated-during-warmup" case .webKitTerminationDidReloadCurrentTab: return "m_d_wktct" case .backgroundTaskSubmissionFailed: return "m_bt_rf" @@ -926,7 +928,7 @@ extension Pixel.Event { case .debugCannotClearObservationsDatabase: return "m_d_cannot_clear_observations_database" case .debugWebsiteDataStoresNotClearedMultiple: return "m_d_wkwebsitedatastoresnotcleared_multiple" case .debugWebsiteDataStoresNotClearedOne: return "m_d_wkwebsitedatastoresnotcleared_one" - case .debugCookieCleanupError: return "m_cookie_cleanup_error" + case .debugCookieCleanupError: return "m_d_cookie-cleanup-error" // MARK: Ad Attribution diff --git a/Core/PreserveLogins.swift b/Core/PreserveLogins.swift index ef1ab7b9b1..2c747a4abd 100644 --- a/Core/PreserveLogins.swift +++ b/Core/PreserveLogins.swift @@ -46,7 +46,6 @@ public class PreserveLogins { return allowedDomains.contains(where: { $0 == cookieDomain || ".\($0)" == cookieDomain || (cookieDomain.hasPrefix(".") && $0.hasSuffix(cookieDomain)) }) - } public func remove(domain: String) { diff --git a/Core/WebCacheManager.swift b/Core/WebCacheManager.swift index d9b91bb2f9..2ea7a18984 100644 --- a/Core/WebCacheManager.swift +++ b/Core/WebCacheManager.swift @@ -17,35 +17,13 @@ // limitations under the License. // -// swiftlint:disable file_length - import Common import WebKit import GRDB -public protocol WebCacheManagerCookieStore { - - func getAllCookies(_ completionHandler: @escaping ([HTTPCookie]) -> Void) - - func setCookie(_ cookie: HTTPCookie, completionHandler: (() -> Void)?) - - func delete(_ cookie: HTTPCookie, completionHandler: (() -> Void)?) - -} - -public protocol WebCacheManagerDataStore { - - var cookieStore: WebCacheManagerCookieStore? { get } - - func legacyClearingRemovingAllDataExceptCookies(completion: @escaping () -> Void) - - func preservedCookies(_ preservedLogins: PreserveLogins) async -> [HTTPCookie] - -} - -extension WebCacheManagerDataStore { +extension WKWebsiteDataStore { - public static func current(dataStoreIdManager: DataStoreIdManager = .shared) -> WebCacheManagerDataStore { + public static func current(dataStoreIdManager: DataStoreIdManager = .shared) -> WKWebsiteDataStore { if #available(iOS 17, *), let id = dataStoreIdManager.id { return WKWebsiteDataStore(forIdentifier: id) } else { @@ -55,96 +33,78 @@ extension WebCacheManagerDataStore { } +extension HTTPCookie { + + func matchesDomain(_ domain: String) -> Bool { + return self.domain == domain || (self.domain.hasPrefix(".") && domain.hasSuffix(self.domain)) + } + +} + +@MainActor public class WebCacheManager { public static var shared = WebCacheManager() - + private init() { } - + /// We save cookies from the current container rather than copying them to a new container because /// the container only persists cookies to disk when the web view is used. If the user presses the fire button /// twice then the fire proofed cookies will be lost and the user will be logged out any sites they're logged in to. public func consumeCookies(cookieStorage: CookieStorage = CookieStorage(), - httpCookieStore: WebCacheManagerCookieStore, - completion: @escaping () -> Void) { + httpCookieStore: WKHTTPCookieStore) async { + guard !cookieStorage.isConsumed else { return } let cookies = cookieStorage.cookies - - guard !cookies.isEmpty, !cookieStorage.isConsumed else { - completion() - return - } - - let group = DispatchGroup() - var consumedCookiesCount = 0 - for cookie in cookies { - group.enter() consumedCookiesCount += 1 - httpCookieStore.setCookie(cookie) { - group.leave() - } - } - - DispatchQueue.global(qos: .userInitiated).async { - group.wait() - cookieStorage.isConsumed = true - - DispatchQueue.main.async { - completion() - - if cookieStorage.cookies.count > 0 { - os_log("Error removing cookies: %d cookies left in CookieStorage", - log: .generalLog, type: .debug, cookieStorage.cookies.count) - - Pixel.fire(pixel: .debugCookieCleanupError, withAdditionalParameters: [ - PixelParameters.count: "\(cookieStorage.cookies.count)" - ]) - } - } + await httpCookieStore.setCookie(cookie) } + cookieStorage.isConsumed = true } - + public func removeCookies(forDomains domains: [String], - dataStore: WebCacheManagerDataStore, - completion: @escaping () -> Void) { - - guard let cookieStore = dataStore.cookieStore else { - completion() - return - } - - cookieStore.getAllCookies { cookies in - let group = DispatchGroup() - cookies.forEach { cookie in - if domains.contains(where: { self.isCookie(cookie, matchingDomain: $0) }) { - group.enter() - cookieStore.delete(cookie) { - group.leave() - } - } + dataStore: WKWebsiteDataStore) async { + + let timeoutTask = Task.detached { + try? await Task.sleep(interval: 5.0) + if !Task.isCancelled { + Pixel.fire(pixel: .cookieDeletionTimedOut, withAdditionalParameters: [ + PixelParameters.removeCookiesTimedOut: "1" + ]) } + } + + let cookieStore = dataStore.httpCookieStore + let cookies = await cookieStore.allCookies() + for cookie in cookies where domains.contains(where: { cookie.matchesDomain($0) }) { + await cookieStore.deleteCookie(cookie) + } + timeoutTask.cancel() + } + + public func clear(cookieStorage: CookieStorage = CookieStorage(), + logins: PreserveLogins = PreserveLogins.shared, + dataStoreIdManager: DataStoreIdManager = .shared) async { - DispatchQueue.global(qos: .userInitiated).async { - let result = group.wait(timeout: .now() + 5) - - if result == .timedOut { - Pixel.fire(pixel: .cookieDeletionTimedOut, withAdditionalParameters: [ - PixelParameters.removeCookiesTimedOut: "1" - ]) - } - - DispatchQueue.main.async { - completion() - } - } + var cookiesToUpdate = [HTTPCookie]() + if #available(iOS 17, *), dataStoreIdManager.hasId { + cookiesToUpdate += await containerBasedClearing(storeIdManager: dataStoreIdManager) ?? [] } + + // Perform legacy clearing to migrate to new container + cookiesToUpdate += await legacyDataClearing() ?? [] + cookieStorage.updateCookies(cookiesToUpdate, keepingPreservedLogins: logins) } + +} +extension WebCacheManager { + @available(iOS 17, *) - func checkForLeftBehindDataStores() async { + private func checkForLeftBehindDataStores() async { let ids = await WKWebsiteDataStore.allDataStoreIdentifiers if ids.count > 1 { Pixel.fire(pixel: .debugWebsiteDataStoresNotClearedMultiple) @@ -154,180 +114,36 @@ public class WebCacheManager { } @available(iOS 17, *) - func containerBasedClearing(cookieStorage: CookieStorage = CookieStorage(), - logins: PreserveLogins, - storeIdManager: DataStoreIdManager, - completion: @escaping () -> Void) { + private func containerBasedClearing(storeIdManager: DataStoreIdManager) async -> [HTTPCookie]? { + guard let containerId = storeIdManager.id else { return [] } + var dataStore: WKWebsiteDataStore? = WKWebsiteDataStore(forIdentifier: containerId) + let cookies = await dataStore?.httpCookieStore.allCookies() + dataStore = nil - guard let containerId = storeIdManager.id else { - completion() - return - } - - Task { @MainActor in - var dataStore: WKWebsiteDataStore? = WKWebsiteDataStore(forIdentifier: containerId) - let cookies = await dataStore?.preservedCookies(logins) - dataStore = nil - - let uuids = await WKWebsiteDataStore.allDataStoreIdentifiers - for uuid in uuids { - try? await WKWebsiteDataStore.remove(forIdentifier: uuid) - } - - await checkForLeftBehindDataStores() - - storeIdManager.allocateNewContainerId() - - cookieStorage.updateCookies(cookies ?? [], keepingPreservedLogins: logins) - - completion() + let uuids = await WKWebsiteDataStore.allDataStoreIdentifiers + for uuid in uuids { + try? await WKWebsiteDataStore.remove(forIdentifier: uuid) } - } - - public func clear(cookieStorage: CookieStorage = CookieStorage(), - logins: PreserveLogins = PreserveLogins.shared, - tabCountInfo: TabCountInfo? = nil, - dataStoreIdManager: DataStoreIdManager = .shared, - completion: @escaping () -> Void) { - - if #available(iOS 17, *), dataStoreIdManager.hasId { - containerBasedClearing(logins: logins, storeIdManager: dataStoreIdManager) { - // Perform legacy clearing anyway, just to be sure - self.legacyDataClearing(logins: logins) { _ in completion() } - } - } else { - legacyDataClearing(logins: logins) { cookies in - if #available(iOS 17, *) { - // From this point onwards... use containers - dataStoreIdManager.allocateNewContainerId() - Task { @MainActor in - cookieStorage.updateCookies(cookies, keepingPreservedLogins: logins) - completion() - } - } else { - completion() - } - } - } - + await checkForLeftBehindDataStores() + + storeIdManager.allocateNewContainerId() + return cookies } - // swiftlint:disable function_body_length - private func legacyDataClearing(logins: PreserveLogins, - tabCountInfo: TabCountInfo? = nil, - completion: @escaping ([HTTPCookie]) -> Void) { - - func keep(_ cookie: HTTPCookie) -> Bool { - return logins.isAllowed(cookieDomain: cookie.domain) || - URL.isDuckDuckGo(domain: cookie.domain) - } - - let dataStore = WKWebsiteDataStore.default() - dataStore.legacyClearingRemovingAllDataExceptCookies { - guard let cookieStore = dataStore.cookieStore else { - completion([]) - return - } - - let cookieClearingSummary = WebStoreCookieClearingSummary() - - cookieStore.getAllCookies { cookies in - let group = DispatchGroup() - let cookiesToRemove = cookies.filter { - !keep($0) - } - - let cookiesToKeep = cookies.filter { - keep($0) - } - - let protectedCookiesCount = cookies.count - cookiesToRemove.count - - cookieClearingSummary.storeInitialCount = cookies.count - cookieClearingSummary.storeProtectedCount = protectedCookiesCount - - for cookie in cookiesToRemove { - group.enter() - cookieStore.delete(cookie) { - group.leave() - } - } - - DispatchQueue.global(qos: .userInitiated).async { - let result = group.wait(timeout: .now() + 5) - - if result == .timedOut { - cookieClearingSummary.didStoreDeletionTimeOut = true - Pixel.fire(pixel: .cookieDeletionTimedOut, withAdditionalParameters: [ - PixelParameters.clearWebDataTimedOut: "1" - ]) - } - - // Remove legacy HTTPCookieStorage cookies - let storageCookies = HTTPCookieStorage.shared.cookies ?? [] - let storageCookiesToRemove = storageCookies.filter { - !logins.isAllowed(cookieDomain: $0.domain) && !URL.isDuckDuckGo(domain: $0.domain) - } - - let protectedStorageCookiesCount = storageCookies.count - storageCookiesToRemove.count - - cookieClearingSummary.storageInitialCount = storageCookies.count - cookieClearingSummary.storageProtectedCount = protectedStorageCookiesCount - - for storageCookie in storageCookiesToRemove { - HTTPCookieStorage.shared.deleteCookie(storageCookie) - } - - self.removeObservationsData() - - self.validateLegacyClearing(for: cookieStore, summary: cookieClearingSummary, tabCountInfo: tabCountInfo) - - DispatchQueue.main.async { - completion(cookiesToKeep) - } - } - } - } - - } - // swiftlint:enable function_body_length - - private func validateLegacyClearing(for cookieStore: WebCacheManagerCookieStore, summary: WebStoreCookieClearingSummary, tabCountInfo: TabCountInfo?) { - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - cookieStore.getAllCookies { cookiesAfterCleaning in - let storageCookiesAfterCleaning = HTTPCookieStorage.shared.cookies ?? [] - - summary.storeAfterDeletionCount = cookiesAfterCleaning.count - summary.storageAfterDeletionCount = storageCookiesAfterCleaning.count - - let cookieStoreDiff = cookiesAfterCleaning.count - summary.storeProtectedCount - let cookieStorageDiff = storageCookiesAfterCleaning.count - summary.storageProtectedCount - - summary.storeAfterDeletionDiffCount = cookieStoreDiff - summary.storageAfterDeletionDiffCount = cookieStorageDiff - - if cookieStoreDiff + cookieStorageDiff > 0 { - os_log("Error removing cookies: %d cookies left in WKHTTPCookieStore, %d cookies left in HTTPCookieStorage", - log: .generalLog, type: .debug, cookieStoreDiff, cookieStorageDiff) - - var parameters = summary.makeDictionaryRepresentation() - - if let tabCountInfo = tabCountInfo { - parameters.merge(tabCountInfo.makeDictionaryRepresentation(), uniquingKeysWith: { _, new in new }) - } - - Pixel.fire(pixel: .cookieDeletionLeftovers, - withAdditionalParameters: parameters) - } + private func legacyDataClearing() async -> [HTTPCookie]? { + let timeoutTask = Task.detached { + if !Task.isCancelled { + Pixel.fire(pixel: .cookieDeletionTimedOut, withAdditionalParameters: [ + PixelParameters.clearWebDataTimedOut: "1" + ]) } } - } - - /// The Fire Button does not delete the user's DuckDuckGo search settings, which are saved as cookies. Removing these cookies would reset them and have undesired consequences, i.e. changing the theme, default language, etc. - /// The Fire Button also does not delete temporary cookies associated with 'surveys.duckduckgo.com'. When we launch surveys to help us understand issues that impact users over time, we use this cookie to temporarily store anonymous survey answers, before deleting the cookie. Cookie storage duration is communicated to users before they opt to submit survey answers. - /// These cookies are not stored in a personally identifiable way. For example, the large size setting is stored as 's=l.' More info in https://duckduckgo.com/privacy - public func isCookie(_ cookie: HTTPCookie, matchingDomain domain: String) -> Bool { - return cookie.domain == domain || (cookie.domain.hasPrefix(".") && domain.hasSuffix(cookie.domain)) + let dataStore = WKWebsiteDataStore.default() + let cookies = await dataStore.httpCookieStore.allCookies() + await dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: .distantPast) + self.removeObservationsData() + timeoutTask.cancel() + return cookies } private func removeObservationsData() { @@ -356,7 +172,6 @@ public class WebCacheManager { return pool } - private func removeObservationsData(from pool: DatabasePool) { do { try pool.write { database in @@ -374,88 +189,3 @@ public class WebCacheManager { } } - -extension WKHTTPCookieStore: WebCacheManagerCookieStore { - -} - -extension WKWebsiteDataStore: WebCacheManagerDataStore { - - @MainActor - public func preservedCookies(_ preservedLogins: PreserveLogins) async -> [HTTPCookie] { - let allCookies = await self.httpCookieStore.allCookies() - return allCookies.filter { - URL.isDuckDuckGo(domain: $0.domain) || preservedLogins.isAllowed(cookieDomain: $0.domain) - } - } - - public var cookieStore: WebCacheManagerCookieStore? { - return self.httpCookieStore - } - - public func legacyClearingRemovingAllDataExceptCookies(completion: @escaping () -> Void) { - var types = WKWebsiteDataStore.allWebsiteDataTypes() - - // Force the HSTS, Media and Alt services cache to clear when using the Fire button. - // https://github.com/WebKit/WebKit/blob/0f73b4d4350c707763146ff0501ab62425c902d6/Source/WebKit/UIProcess/API/Cocoa/WKWebsiteDataRecord.mm#L47 - types.insert("_WKWebsiteDataTypeHSTSCache") - types.insert("_WKWebsiteDataTypeMediaKeys") - types.insert("_WKWebsiteDataTypeAlternativeServices") - types.insert("_WKWebsiteDataTypeSearchFieldRecentSearches") - types.insert("_WKWebsiteDataTypeResourceLoadStatistics") - types.insert("_WKWebsiteDataTypeCredentials") - types.insert("_WKWebsiteDataTypeAdClickAttributions") - types.insert("_WKWebsiteDataTypePrivateClickMeasurements") - - types.remove(WKWebsiteDataTypeCookies) - - removeData(ofTypes: types, - modifiedSince: Date.distantPast, - completionHandler: completion) - } - -} - -final class WebStoreCookieClearingSummary { - var storeInitialCount: Int = 0 - var storeProtectedCount: Int = 0 - var didStoreDeletionTimeOut: Bool = false - var storageInitialCount: Int = 0 - var storageProtectedCount: Int = 0 - - var storeAfterDeletionCount: Int = 0 - var storageAfterDeletionCount: Int = 0 - var storeAfterDeletionDiffCount: Int = 0 - var storageAfterDeletionDiffCount: Int = 0 - - func makeDictionaryRepresentation() -> [String: String] { - [PixelParameters.storeInitialCount: "\(storeInitialCount)", - PixelParameters.storeProtectedCount: "\(storeProtectedCount)", - PixelParameters.didStoreDeletionTimeOut: didStoreDeletionTimeOut ? "true" : "false", - PixelParameters.storageInitialCount: "\(storageInitialCount)", - PixelParameters.storageProtectedCount: "\(storageProtectedCount)", - PixelParameters.storeAfterDeletionCount: "\(storeAfterDeletionCount)", - PixelParameters.storageAfterDeletionCount: "\(storageAfterDeletionCount)", - PixelParameters.storeAfterDeletionDiffCount: "\(storeAfterDeletionDiffCount)", - PixelParameters.storageAfterDeletionDiffCount: "\(storageAfterDeletionDiffCount)"] - } -} - -public final class TabCountInfo { - var tabsModelCount: Int = 0 - var tabControllerCacheCount: Int = 0 - - public init() { } - - public init(tabsModelCount: Int, tabControllerCacheCount: Int) { - self.tabsModelCount = tabsModelCount - self.tabControllerCacheCount = tabControllerCacheCount - } - - func makeDictionaryRepresentation() -> [String: String] { - [PixelParameters.tabsModelCount: "\(tabsModelCount)", - PixelParameters.tabControllerCacheCount: "\(tabControllerCacheCount)"] - } -} - -// swiftlint:enable file_length diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c796e82d7a..5ce4cc0b17 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -440,6 +440,7 @@ 8590CB632684F10F0089F6BF /* ContentBlockerProtectionStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590CB622684F10F0089F6BF /* ContentBlockerProtectionStoreTests.swift */; }; 8590CB67268A2E520089F6BF /* RootDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590CB66268A2E520089F6BF /* RootDebugViewController.swift */; }; 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8590CB68268A4E190089F6BF /* DebugEtagStorage.swift */; }; + 8596C30D2B7EB1800058EF90 /* DataStoreWarmup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8596C30C2B7EB1800058EF90 /* DataStoreWarmup.swift */; }; 8598F67B2405EB8D00FBC70C /* KeyboardSettingsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8598F6792405EB8600FBC70C /* KeyboardSettingsTests.swift */; }; 8599690F29D2F1C100DBF9FA /* DDGSync in Frameworks */ = {isa = PBXBuildFile; productRef = 8599690E29D2F1C100DBF9FA /* DDGSync */; }; 85A1B3B220C6CD9900C18F15 /* CookieStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85A1B3B120C6CD9900C18F15 /* CookieStorage.swift */; }; @@ -1528,6 +1529,7 @@ 8590CB622684F10F0089F6BF /* ContentBlockerProtectionStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerProtectionStoreTests.swift; sourceTree = ""; }; 8590CB66268A2E520089F6BF /* RootDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootDebugViewController.swift; sourceTree = ""; }; 8590CB68268A4E190089F6BF /* DebugEtagStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugEtagStorage.swift; sourceTree = ""; }; + 8596C30C2B7EB1800058EF90 /* DataStoreWarmup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataStoreWarmup.swift; sourceTree = ""; }; 8598F6792405EB8600FBC70C /* KeyboardSettingsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardSettingsTests.swift; sourceTree = ""; }; 85A1B3B120C6CD9900C18F15 /* CookieStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieStorage.swift; sourceTree = ""; }; 85A313962028E78A00327D00 /* release_notes.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = release_notes.txt; path = fastlane/metadata/default/release_notes.txt; sourceTree = ""; }; @@ -5081,6 +5083,7 @@ F1A886771F29394E0096251E /* WebCacheManager.swift */, 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */, 830381BF1F850AAF00863075 /* WKWebViewConfigurationExtension.swift */, + 8596C30C2B7EB1800058EF90 /* DataStoreWarmup.swift */, ); name = Web; sourceTree = ""; @@ -7101,6 +7104,7 @@ 9833913727AC400800DAF119 /* AppTrackerDataSetProvider.swift in Sources */, 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */, 853A717620F62FE800FE60BC /* Pixel.swift in Sources */, + 8596C30D2B7EB1800058EF90 /* DataStoreWarmup.swift in Sources */, 4B470EDB299C4FB20086EBDC /* AppTrackingProtectionListViewModel.swift in Sources */, F41C2DA526C1975E00F9A760 /* BookmarksCoreDataStorage.swift in Sources */, 9876B75E2232B36900D81D9F /* TabInstrumentation.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index c9bfe95a32..8e81eb22af 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -287,7 +287,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } autoClear = AutoClear(worker: main) - autoClear?.applicationDidLaunch() AppDependencyProvider.shared.voiceSearchHelper.migrateSettingsFlagIfNecessary() @@ -524,7 +523,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { Task { @MainActor in await beginAuthentication() - autoClear?.applicationWillMoveToForeground() + await autoClear?.applicationWillMoveToForeground() showKeyboardIfSettingOn = true syncService.scheduler.resumeSyncQueue() } @@ -581,11 +580,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { mainViewController?.clearNavigationStack() } - autoClear?.applicationWillMoveToForeground() - showKeyboardIfSettingOn = false + Task { @MainActor in + await autoClear?.applicationWillMoveToForeground() + showKeyboardIfSettingOn = false - if !handleAppDeepLink(app, mainViewController, url) { - mainViewController?.loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) + if !handleAppDeepLink(app, mainViewController, url) { + mainViewController?.loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: nil) + } } return true @@ -695,19 +696,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func handleShortCutItem(_ shortcutItem: UIApplicationShortcutItem) { os_log("Handling shortcut item: %s", log: .generalLog, type: .debug, shortcutItem.type) - autoClear?.applicationWillMoveToForeground() + Task { @MainActor in + + await autoClear?.applicationWillMoveToForeground() - if shortcutItem.type == ShortcutKey.clipboard, let query = UIPasteboard.general.string { - mainViewController?.clearNavigationStack() - mainViewController?.loadQueryInNewTab(query) - return - } + if shortcutItem.type == ShortcutKey.clipboard, let query = UIPasteboard.general.string { + mainViewController?.clearNavigationStack() + mainViewController?.loadQueryInNewTab(query) + return + } #if NETWORK_PROTECTION - if shortcutItem.type == ShortcutKey.openVPNSettings { - presentNetworkProtectionStatusSettingsModal() - } + if shortcutItem.type == ShortcutKey.openVPNSettings { + presentNetworkProtectionStatusSettingsModal() + } #endif + + } } private func removeEmailWaitlistState() { diff --git a/DuckDuckGo/AutoClear.swift b/DuckDuckGo/AutoClear.swift index 79359dbc4f..eca57dce95 100644 --- a/DuckDuckGo/AutoClear.swift +++ b/DuckDuckGo/AutoClear.swift @@ -23,8 +23,9 @@ import UIKit protocol AutoClearWorker { func clearNavigationStack() - func forgetData() + func forgetData() async func forgetTabs() + func clearDataFinished(_: AutoClear) } class AutoClear { @@ -42,25 +43,19 @@ class AutoClear { self.worker = worker } - private func clearData() { + @MainActor + private func clearData() async { guard let settings = AutoClearSettingsModel(settings: appSettings) else { return } - if settings.action.contains(.clearData) { - worker.forgetData() - } - if settings.action.contains(.clearTabs) { worker.forgetTabs() } - } - - func applicationDidLaunch() { - guard let settings = AutoClearSettingsModel(settings: appSettings) else { return } - - // Note: for startup, we clear only Data, as TabsModel is cleared on load + if settings.action.contains(.clearData) { - worker.forgetData() + await worker.forgetData() } + + worker.clearDataFinished(self) } /// Note: function is parametrised because of tests. @@ -85,13 +80,14 @@ class AutoClear { } } - func applicationWillMoveToForeground() { + @MainActor + func applicationWillMoveToForeground() async { guard isClearingEnabled, let timestamp = timestamp, shouldClearData(elapsedTime: Date().timeIntervalSince1970 - timestamp) else { return } worker.clearNavigationStack() - clearData() + await clearData() self.timestamp = nil } } diff --git a/DuckDuckGo/CookieDebugViewController.swift b/DuckDuckGo/CookieDebugViewController.swift index 0ddb0960d4..ed2b84fb16 100644 --- a/DuckDuckGo/CookieDebugViewController.swift +++ b/DuckDuckGo/CookieDebugViewController.swift @@ -46,9 +46,13 @@ class CookieDebugViewController: UITableViewController { } private func fetchCookies() { - WKWebsiteDataStore.default().cookieStore?.getAllCookies(displayCookies) + Task { @MainActor in + let dataStore = WKWebsiteDataStore.current() + displayCookies(cookies: await dataStore.httpCookieStore.allCookies()) + } } + @MainActor private func displayCookies(cookies: [HTTPCookie]) { self.loaded = true diff --git a/DuckDuckGo/FireButtonAnimator.swift b/DuckDuckGo/FireButtonAnimator.swift index e153dcc30c..ef8c2af543 100644 --- a/DuckDuckGo/FireButtonAnimator.swift +++ b/DuckDuckGo/FireButtonAnimator.swift @@ -110,20 +110,24 @@ class FireButtonAnimator { object: nil) } - func animate(onAnimationStart: @escaping () -> Void, onTransitionCompleted: @escaping () -> Void, completion: @escaping () -> Void) { - + func animate(onAnimationStart: @escaping () async -> Void, onTransitionCompleted: @escaping () async -> Void, completion: @escaping () async -> Void) { + guard let window = UIApplication.shared.windows.filter({ $0.isKeyWindow }).first, let snapshot = window.snapshotView(afterScreenUpdates: false) else { - onAnimationStart() - onTransitionCompleted() - completion() + Task { @MainActor in + await onAnimationStart() + await onTransitionCompleted() + await completion() + } return } guard let composition = preLoadedComposition else { - onAnimationStart() - onTransitionCompleted() - completion() + Task { @MainActor in + await onAnimationStart() + await onTransitionCompleted() + await completion() + } return } @@ -141,16 +145,22 @@ class FireButtonAnimator { let delay = duration * currentAnimation.transition DispatchQueue.main.asyncAfter(deadline: .now() + delay) { snapshot.removeFromSuperview() - onTransitionCompleted() + Task { @MainActor in + await onTransitionCompleted() + } } animationView.play(fromProgress: 0, toProgress: 1) { [weak animationView] _ in animationView?.removeFromSuperview() - completion() + Task { @MainActor in + await completion() + } } DispatchQueue.main.async { - onAnimationStart() + Task { @MainActor in + await onAnimationStart() + } } } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 8799d48c8b..c4b19b187d 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -154,6 +154,7 @@ class MainViewController: UIViewController { var postClear: (() -> Void)? var clearInProgress = false + var dataStoreWarmup: DataStoreWarmup? = DataStoreWarmup() required init?(coder: NSCoder) { fatalError("Use init?(code:") @@ -205,11 +206,9 @@ class MainViewController: UIViewController { bindSyncService() } #endif - - fileprivate var tabCountInfo: TabCountInfo? - + func loadFindInPage() { - + let view = FindInPageView.loadFromXib() self.view.addSubview(view) @@ -682,11 +681,18 @@ class MainViewController: UIViewController { let isPadDevice = UIDevice.current.userInterfaceIdiom == .pad let tabsModel: TabsModel - let shouldClearTabsModelOnStartup = AutoClearSettingsModel(settings: appSettings) != nil - if shouldClearTabsModelOnStartup { + if let settings = AutoClearSettingsModel(settings: appSettings) { + // This needs to be refactored so that tabs model is injected and cleared before view did load, + // but for now, ensure this happens in the right order by clearing data here too, if needed. tabsModel = TabsModel(desktop: isPadDevice) tabsModel.save() previewsSource.removeAllPreviews() + + if settings.action.contains(.clearData) { + Task { @MainActor in + await forgetData() + } + } } else { if let storedModel = TabsModel.get() { // Save new model in case of migration @@ -2158,6 +2164,7 @@ extension MainViewController: AutoClearWorker { } func forgetTabs() { + omniBar.resignFirstResponder() findInPageView?.done() tabManager.removeAll() } @@ -2169,33 +2176,42 @@ extension MainViewController: AutoClearWorker { swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) Favicons.shared.clearCache(.tabs) } - - func forgetData() { + + @MainActor + func clearDataFinished(_: AutoClear) { + refreshUIAfterClear() + } + + func forgetData() async { guard !clearInProgress else { assertionFailure("Shouldn't get called multiple times") return } clearInProgress = true + + // This needs to happen only once per app launch + if let dataStoreWarmup { + await dataStoreWarmup.ensureReady() + self.dataStoreWarmup = nil + } + URLSession.shared.configuration.urlCache?.removeAllCachedResponses() let pixel = TimedPixel(.forgetAllDataCleared) - WebCacheManager.shared.clear(tabCountInfo: tabCountInfo) { - pixel.fire(withAdditionalParameters: [PixelParameters.tabCount: "\(self.tabManager.count)"]) + await WebCacheManager.shared.clear() + pixel.fire(withAdditionalParameters: [PixelParameters.tabCount: "\(self.tabManager.count)"]) - AutoconsentManagement.shared.clearCache() - DaxDialogs.shared.clearHeldURLData() + AutoconsentManagement.shared.clearCache() + DaxDialogs.shared.clearHeldURLData() - if self.syncService.authState == .inactive { - self.bookmarksDatabaseCleaner?.cleanUpDatabaseNow() - } - - self.refreshUIAfterClear() - self.clearInProgress = false - - self.postClear?() - self.postClear = nil + if self.syncService.authState == .inactive { + self.bookmarksDatabaseCleaner?.cleanUpDatabaseNow() } + self.clearInProgress = false + + self.postClear?() + self.postClear = nil } func stopAllOngoingDownloads() { @@ -2206,22 +2222,22 @@ extension MainViewController: AutoClearWorker { let spid = Instruments.shared.startTimedEvent(.clearingData) Pixel.fire(pixel: .forgetAllExecuted) - self.tabCountInfo = tabManager.makeTabCountInfo() - tabManager.prepareAllTabsExceptCurrentForDataClearing() fireButtonAnimator.animate { self.tabManager.prepareCurrentTabForDataClearing() self.stopAllOngoingDownloads() self.forgetTabs() - self.forgetData() + await self.forgetData() + Instruments.shared.endTimedEvent(for: spid) DaxDialogs.shared.resumeRegularFlow() } onTransitionCompleted: { ActionMessageView.present(message: UserText.actionForgetAllDone, presentationLocation: .withBottomBar(andAddressBarBottom: self.appSettings.currentAddressBarPosition.isBottom)) transitionCompletion?() + self.refreshUIAfterClear() } completion: { - Instruments.shared.endTimedEvent(for: spid) + // Ideally this should happen once data clearing has finished AND the animation is finished if showNextDaxDialog { self.homeController?.showNextDaxDialog() } else if KeyboardSettings().onNewTab { diff --git a/DuckDuckGo/PreserveLoginsSettingsViewController.swift b/DuckDuckGo/PreserveLoginsSettingsViewController.swift index d243ed1315..027099470a 100644 --- a/DuckDuckGo/PreserveLoginsSettingsViewController.swift +++ b/DuckDuckGo/PreserveLoginsSettingsViewController.swift @@ -142,11 +142,10 @@ class PreserveLoginsSettingsViewController: UITableViewController { override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { guard editingStyle == .delete else { return } - + let domain = model.remove(at: indexPath.row) PreserveLogins.shared.remove(domain: domain) Favicons.shared.removeFireproofFavicon(forDomain: domain) - WebCacheManager.shared.removeCookies(forDomains: [domain], dataStore: WKWebsiteDataStore.current()) { } if self.model.isEmpty { self.endEditing() @@ -154,6 +153,10 @@ class PreserveLoginsSettingsViewController: UITableViewController { } else { tableView.deleteRows(at: [indexPath], with: .automatic) } + + Task { @MainActor in + await WebCacheManager.shared.removeCookies(forDomains: [domain], dataStore: WKWebsiteDataStore.current()) + } } override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { @@ -209,10 +212,12 @@ class PreserveLoginsSettingsViewController: UITableViewController { PreserveLoginsAlert.showClearAllAlert(usingController: self, cancelled: { [weak self] in self?.refreshModel() }, confirmed: { [weak self] in - WebCacheManager.shared.removeCookies(forDomains: self?.model ?? [], dataStore: WKWebsiteDataStore.current()) { } - PreserveLogins.shared.clearAll() - self?.refreshModel() - self?.endEditing() + Task { @MainActor in + await WebCacheManager.shared.removeCookies(forDomains: self?.model ?? [], dataStore: WKWebsiteDataStore.current()) + PreserveLogins.shared.clearAll() + self?.refreshModel() + self?.endEditing() + } }) } diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index d54d1dec05..e79147a914 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -249,7 +249,7 @@ class DiagnosticReportDataSource: UIActivityItemProvider { let group = DispatchGroup() group.enter() DispatchQueue.main.async { - WKWebsiteDataStore.default().cookieStore?.getAllCookies { httpCookies in + WKWebsiteDataStore.current().httpCookieStore.getAllCookies { httpCookies in cookies = httpCookies group.leave() } diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 0ca7f51639..39e2629895 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -225,7 +225,6 @@ class TabManager { model.clearAll() for controller in tabControllerCache { removeFromCache(controller) - // controller.prepareForDataClearing() } save() } @@ -326,11 +325,3 @@ extension TabManager { } } } - -extension TabManager { - - func makeTabCountInfo() -> TabCountInfo { - TabCountInfo(tabsModelCount: model.count, - tabControllerCacheCount: tabControllerCache.count) - } -} diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 21e2e3925b..b0c7df98ea 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -489,16 +489,11 @@ class TabViewController: UIViewController { } } - webView.configuration.websiteDataStore.fetchDataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) { _ in - guard let cookieStore = self.webView.configuration.websiteDataStore.cookieStore else { - doLoad() - return - } - - WebCacheManager.shared.consumeCookies(httpCookieStore: cookieStore) { [weak self] in - guard let strongSelf = self else { return } - doLoad() - } + Task { @MainActor in + await webView.configuration.websiteDataStore.dataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) + let cookieStore = webView.configuration.websiteDataStore.httpCookieStore + await WebCacheManager.shared.consumeCookies(httpCookieStore: cookieStore) + doLoad() } } diff --git a/DuckDuckGoTests/AutoClearTests.swift b/DuckDuckGoTests/AutoClearTests.swift index 71a2a4dd1c..2edb1e52ee 100644 --- a/DuckDuckGoTests/AutoClearTests.swift +++ b/DuckDuckGoTests/AutoClearTests.swift @@ -28,7 +28,8 @@ class AutoClearTests: XCTestCase { var clearNavigationStackInvocationCount = 0 var forgetDataInvocationCount = 0 var forgetTabsInvocationCount = 0 - + var clearDataFinishedInvocationCount = 0 + func clearNavigationStack() { clearNavigationStackInvocationCount += 1 } @@ -40,6 +41,10 @@ class AutoClearTests: XCTestCase { func forgetTabs() { forgetTabsInvocationCount += 1 } + + func clearDataFinished(_: AutoClear) { + clearDataFinishedInvocationCount += 1 + } } private var worker: MockWorker! @@ -51,59 +56,23 @@ class AutoClearTests: XCTestCase { worker = MockWorker() logic = AutoClear(worker: worker) } - - func testWhenModeIsSetToCleanDataThenDataIsCleared() { - let appSettings = AppUserDefaults() - appSettings.autoClearAction = .clearData - appSettings.autoClearTiming = .termination - - logic.applicationDidLaunch() - - XCTAssertEqual(worker.forgetDataInvocationCount, 1) - XCTAssertEqual(worker.forgetTabsInvocationCount, 0) - } - - func testWhenModeIsSetToCleanTabsAndDataThenDataIsCleared() { - let appSettings = AppUserDefaults() - appSettings.autoClearAction = [.clearData, .clearTabs] - appSettings.autoClearTiming = .termination - - logic.applicationDidLaunch() - - XCTAssertEqual(worker.forgetDataInvocationCount, 1) - - // Tabs are cleared when loading TabsModel for the first time - XCTAssertEqual(worker.forgetTabsInvocationCount, 0) - } - - func testWhenModeIsNotSetThenNothingIsCleared() { - let appSettings = AppUserDefaults() - appSettings.autoClearAction = [] - appSettings.autoClearTiming = .termination - - logic.applicationDidLaunch() - - XCTAssertEqual(worker.forgetDataInvocationCount, 0) - XCTAssertEqual(worker.forgetTabsInvocationCount, 0) - } - - func testWhenTimingIsSetToTerminationThenOnlyRestartClearsData() { + + // Note: applicationDidLaunch based clearing has moved to "configureTabManager" function of + // MainViewController to ensure that tabs are removed before the data is cleared. + + func testWhenTimingIsSetToTerminationThenOnlyRestartClearsData() async { let appSettings = AppUserDefaults() appSettings.autoClearAction = .clearData appSettings.autoClearTiming = .termination - logic.applicationWillMoveToForeground() + await logic.applicationWillMoveToForeground() logic.applicationDidEnterBackground() XCTAssertEqual(worker.clearNavigationStackInvocationCount, 0) XCTAssertEqual(worker.forgetDataInvocationCount, 0) - - logic.applicationDidLaunch() - - XCTAssertEqual(worker.forgetDataInvocationCount, 1) } - func testWhenDesiredTimingIsSetThenDataIsClearedOnceTimeHasElapsed() { + func testWhenDesiredTimingIsSetThenDataIsClearedOnceTimeHasElapsed() async { let appSettings = AppUserDefaults() appSettings.autoClearAction = .clearData @@ -117,13 +86,13 @@ class AutoClearTests: XCTestCase { appSettings.autoClearTiming = timing logic.applicationDidEnterBackground(Date().timeIntervalSince1970 - delay + 1) - logic.applicationWillMoveToForeground() + await logic.applicationWillMoveToForeground() XCTAssertEqual(worker.clearNavigationStackInvocationCount, iterationCount) XCTAssertEqual(worker.forgetDataInvocationCount, iterationCount) logic.applicationDidEnterBackground(Date().timeIntervalSince1970 - delay - 1) - logic.applicationWillMoveToForeground() + await logic.applicationWillMoveToForeground() iterationCount += 1 XCTAssertEqual(worker.clearNavigationStackInvocationCount, iterationCount) diff --git a/DuckDuckGoTests/CookieStorageTests.swift b/DuckDuckGoTests/CookieStorageTests.swift index 4a78e3e006..07927f155c 100644 --- a/DuckDuckGoTests/CookieStorageTests.swift +++ b/DuckDuckGoTests/CookieStorageTests.swift @@ -35,8 +35,25 @@ public class CookieStorageTests: XCTestCase { let defaults = UserDefaults(suiteName: Self.userDefaultsSuiteName)! defaults.removePersistentDomain(forName: Self.userDefaultsSuiteName) storage = CookieStorage(userDefaults: defaults) + storage.isConsumed = true logins.clearAll() } + + func testWhenDomainRemovesAllCookesThenTheyAreClearedFromPersisted() { + logins.addToAllowed(domain: "example.com") + + XCTAssertEqual(storage.updateCookies([ + make("example.com", name: "x", value: "1"), + ], keepingPreservedLogins: logins), .empty) + + XCTAssertEqual(1, storage.cookies.count) + + storage.isConsumed = true + storage.updateCookies([], keepingPreservedLogins: logins) + + XCTAssertEqual(0, storage.cookies.count) + + } func testWhenUpdatedThenDuckDuckGoCookiesAreNotRemoved() { storage.updateCookies([ @@ -45,14 +62,19 @@ public class CookieStorageTests: XCTestCase { XCTAssertEqual(1, storage.cookies.count) + storage.isConsumed = true storage.updateCookies([ + make("duckduckgo.com", name: "x", value: "1"), make("test.com", name: "x", value: "1"), ], keepingPreservedLogins: logins) XCTAssertEqual(2, storage.cookies.count) + storage.isConsumed = true storage.updateCookies([ make("usedev1.duckduckgo.com", name: "x", value: "1"), + make("duckduckgo.com", name: "x", value: "1"), + make("test.com", name: "x", value: "1"), ], keepingPreservedLogins: logins) XCTAssertEqual(3, storage.cookies.count) @@ -62,9 +84,6 @@ public class CookieStorageTests: XCTestCase { func testWhenUpdatedThenCookiesWithFutureExpirationAreNotRemoved() { storage.updateCookies([ make("test.com", name: "x", value: "1", expires: .distantFuture), - ], keepingPreservedLogins: logins) - - storage.updateCookies([ make("example.com", name: "x", value: "1"), ], keepingPreservedLogins: logins) @@ -80,6 +99,7 @@ public class CookieStorageTests: XCTestCase { ] XCTAssertEqual(1, storage.cookies.count) + storage.isConsumed = true storage.updateCookies([ make("example.com", name: "x", value: "1"), ], keepingPreservedLogins: logins) @@ -108,6 +128,7 @@ public class CookieStorageTests: XCTestCase { logins.remove(domain: "test.com") + storage.isConsumed = true storage.updateCookies([ make("example.com", name: "x", value: "1"), ], keepingPreservedLogins: logins) @@ -117,9 +138,9 @@ public class CookieStorageTests: XCTestCase { XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "example.com" })) } - func testWhenStorageInitialiedThenItIsEmptyAndConsumedIsFalse() { + func testWhenStorageInitialiedThenItIsEmptyAndIsReadyToBeUpdated() { XCTAssertEqual(0, storage.cookies.count) - XCTAssertEqual(false, storage.isConsumed) + XCTAssertTrue(storage.isConsumed) } func testWhenStorageIsUpdatedThenConsumedIsResetToFalse() { @@ -149,26 +170,12 @@ public class CookieStorageTests: XCTestCase { XCTAssertEqual(1, storage.cookies.count) } - func testWhenStorageIsUpdatedThenExistingCookiesAreUnaffected() { - storage.updateCookies([ - make("test.com", name: "x", value: "1"), - make("example.com", name: "x", value: "1"), - ], keepingPreservedLogins: logins) - - storage.updateCookies([ - make("example.com", name: "x", value: "2"), - ], keepingPreservedLogins: logins) - - XCTAssertEqual(2, storage.cookies.count) - XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "test.com" && $0.name == "x" && $0.value == "1" })) - XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "example.com" && $0.name == "x" && $0.value == "2" })) - } - func testWhenStorageHasMatchingDOmainThenUpdatingReplacesCookies() { storage.updateCookies([ make("test.com", name: "x", value: "1") ], keepingPreservedLogins: logins) + storage.isConsumed = true storage.updateCookies([ make("test.com", name: "x", value: "2"), make("test.com", name: "y", value: "3"), @@ -180,6 +187,19 @@ public class CookieStorageTests: XCTestCase { XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "test.com" && $0.name == "y" && $0.value == "3" })) } + func testWhenStorageUpdatedAndNotConsumedThenNothingHappens() { + storage.updateCookies([ + make("test.com", name: "x", value: "1") + ], keepingPreservedLogins: logins) + + storage.updateCookies([ + make("example.com", name: "y", value: "3"), + ], keepingPreservedLogins: logins) + + XCTAssertEqual(1, storage.cookies.count) + XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "test.com" && $0.name == "x" && $0.value == "1" })) + } + func make(_ domain: String, name: String, value: String, expires: Date? = nil) -> HTTPCookie { logins.addToAllowed(domain: domain) return HTTPCookie(properties: [ diff --git a/DuckDuckGoTests/DownloadManagerTests.swift b/DuckDuckGoTests/DownloadManagerTests.swift index b4d428d268..86367d7144 100644 --- a/DuckDuckGoTests/DownloadManagerTests.swift +++ b/DuckDuckGoTests/DownloadManagerTests.swift @@ -33,7 +33,21 @@ class DownloadManagerTests: XCTestCase { downloadManagerTestsHelper.deleteAllFiles() } - func testNotificationTemporaryPKPassDownload() { + func testWhenIPadThenPKPassThenDownloadIsNotTemporary() { + guard UIDevice.current.userInterfaceIdiom == .pad else { return } + + let notificationCenter = NotificationCenter() + let downloadManager = DownloadManager(notificationCenter) + + let sessionSetup = MockSessionSetup(mimeType: "application/vnd.apple.pkpass", downloadManager: downloadManager) + + let download = downloadManager.makeDownload(navigationResponse: sessionSetup.response, downloadSession: sessionSetup.session)! + XCTAssertFalse(download.temporary, "Download should be not temporary") + } + + func testNotificationTemporaryPKPassDownloadOnPhone() { + guard UIDevice.current.userInterfaceIdiom == .phone else { return } + let notificationCenter = NotificationCenter() let downloadManager = DownloadManager(notificationCenter) diff --git a/DuckDuckGoTests/FireButtonReferenceTests.swift b/DuckDuckGoTests/FireButtonReferenceTests.swift index 6e1ea46555..5b45c0a642 100644 --- a/DuckDuckGoTests/FireButtonReferenceTests.swift +++ b/DuckDuckGoTests/FireButtonReferenceTests.swift @@ -44,7 +44,10 @@ final class FireButtonReferenceTests: XCTestCase { return url.host! } - func testClearData() async throws { + @MainActor + func testClearDataUsingLegacyContainer() async throws { + // Using WKWebsiteDataStore(forIdentifier:) doesn't persist cookies in a testable way, so use the legacy container here. + let preservedLogins = PreserveLogins.shared preservedLogins.clearAll() @@ -57,23 +60,22 @@ final class FireButtonReferenceTests: XCTestCase { let referenceTests = testData.fireButtonFireproofing.tests.filter { $0.exceptPlatforms.contains("ios-browser") == false } - + let cookieStorage = CookieStorage() + let idManager = DataStoreIdManager() + XCTAssertFalse(idManager.hasId) + for test in referenceTests { let cookie = try XCTUnwrap(cookie(for: test)) + + let cookieStore = WKWebsiteDataStore.default().httpCookieStore + await cookieStore.setCookie(cookie) - // Set directly to avoid logic to remove non-preserved cookies - cookieStorage.cookies = [ - cookie - ] + // Pretend the webview was loaded and the cookies were previously consumed + cookieStorage.isConsumed = true - idManager.allocateNewContainerId() - await withCheckedContinuation { continuation in - WebCacheManager.shared.clear(cookieStorage: cookieStorage, logins: preservedLogins, dataStoreIdManager: idManager) { - continuation.resume() - } - } + await WebCacheManager.shared.clear(cookieStorage: cookieStorage, logins: preservedLogins, dataStoreIdManager: idManager) let testCookie = cookieStorage.cookies.filter { $0.name == test.cookieName }.first @@ -104,13 +106,18 @@ final class FireButtonReferenceTests: XCTestCase { } let cookieStorage = CookieStorage() + cookieStorage.isConsumed = true for test in referenceTests { let cookie = try XCTUnwrap(cookie(for: test)) + + // Pretend the webview was loaded and the cookies were previously consumed + cookieStorage.isConsumed = true + // This simulates loading the cookies from the current web view data stores and updating the storage cookieStorage.updateCookies([ cookie ], keepingPreservedLogins: preservedLogins) - + let testCookie = cookieStorage.cookies.filter { $0.name == test.cookieName }.first if test.expectCookieRemoved { @@ -121,6 +128,7 @@ final class FireButtonReferenceTests: XCTestCase { // Reset cache cookieStorage.cookies = [] + cookieStorage.isConsumed = true } } diff --git a/DuckDuckGoTests/WebCacheManagerTests.swift b/DuckDuckGoTests/WebCacheManagerTests.swift index b0c3dc2b2c..ab435d637e 100644 --- a/DuckDuckGoTests/WebCacheManagerTests.swift +++ b/DuckDuckGoTests/WebCacheManagerTests.swift @@ -27,6 +27,8 @@ class WebCacheManagerTests: XCTestCase { override func setUp() { super.setUp() + CookieStorage().cookies = [] + CookieStorage().isConsumed = true UserDefaults.standard.removeObject(forKey: UserDefaultsWrapper.Key.webContainerId.rawValue) if #available(iOS 17, *) { WKWebsiteDataStore.fetchAllDataStoreIdentifiers { uuids in @@ -59,16 +61,15 @@ class WebCacheManagerTests: XCTestCase { let loadedCount = await defaultStore.httpCookieStore.allCookies().count XCTAssertEqual(5, loadedCount) - await withCheckedContinuation { continuation in - WebCacheManager.shared.clear(logins: logins, dataStoreIdManager: dataStoreIdManager) { - continuation.resume() - } - } + let cookieStore = CookieStorage() + await WebCacheManager.shared.clear(cookieStorage: cookieStore, logins: logins, dataStoreIdManager: dataStoreIdManager) let cookies = await defaultStore.httpCookieStore.allCookies() - XCTAssertEqual(cookies.count, 2) - XCTAssertTrue(cookies.contains(where: { $0.domain == ".twitter.com" })) - XCTAssertTrue(cookies.contains(where: { $0.domain == "mobile.twitter.com" })) + XCTAssertEqual(cookies.count, 0) + + XCTAssertEqual(2, cookieStore.cookies.count) + XCTAssertTrue(cookieStore.cookies.contains(where: { $0.domain == ".twitter.com" })) + XCTAssertTrue(cookieStore.cookies.contains(where: { $0.domain == "mobile.twitter.com" })) } @MainActor @@ -83,18 +84,13 @@ class WebCacheManagerTests: XCTestCase { await defaultStore.httpCookieStore.setCookie(.make(domain: "www.example.com")) await defaultStore.httpCookieStore.setCookie(.make(domain: ".example.com")) - await withCheckedContinuation { continuation in - WebCacheManager.shared.removeCookies(forDomains: ["www.example.com"], dataStore: WKWebsiteDataStore.current()) { - continuation.resume() - } - } + await WebCacheManager.shared.removeCookies(forDomains: ["www.example.com"], dataStore: WKWebsiteDataStore.current()) let cookies = await defaultStore.httpCookieStore.allCookies() XCTAssertEqual(cookies.count, 0) } @MainActor func testWhenClearedThenCookiesWithParentDomainsAreRetained() async { - let logins = MockPreservedLogins(domains: [ "www.example.com" ]) @@ -109,40 +105,47 @@ class WebCacheManagerTests: XCTestCase { await defaultStore.httpCookieStore.setCookie(.make(domain: "example.com")) await defaultStore.httpCookieStore.setCookie(.make(domain: ".example.com")) - await withCheckedContinuation { continuation in - WebCacheManager.shared.clear(logins: logins, dataStoreIdManager: dataStoreIdManager) { - continuation.resume() - } - } - + let cookieStorage = CookieStorage() + + await WebCacheManager.shared.clear(cookieStorage: cookieStorage, + logins: logins, + dataStoreIdManager: dataStoreIdManager) let cookies = await defaultStore.httpCookieStore.allCookies() - XCTAssertEqual(cookies.count, 1) - XCTAssertEqual(cookies[0].domain, ".example.com") + XCTAssertEqual(cookies.count, 0) + XCTAssertEqual(cookieStorage.cookies.count, 1) + XCTAssertEqual(cookieStorage.cookies[0].domain, ".example.com") } - func testWhenClearedThenDDGCookiesAreRetained() { + @MainActor + @available(iOS 17, *) + func testWhenClearedWithDataStoreContainerThenDDGCookiesAreRetained() async throws { + throw XCTSkip("WKWebsiteDataStore(forIdentifier:) does not persist cookies properly until attached to a running webview") + + // This test should look like `testWhenClearedWithLegacyContainerThenDDGCookiesAreRetained` but + // with a container ID set on the `dataStoreIdManager`. + } + + @MainActor + func testWhenClearedWithLegacyContainerThenDDGCookiesAreRetained() async { let logins = MockPreservedLogins(domains: [ "www.example.com" ]) + + XCTAssertFalse(dataStoreIdManager.hasId) + + let cookieStore = WKWebsiteDataStore.default().httpCookieStore + await cookieStore.setCookie(.make(name: "name", value: "value", domain: "duckduckgo.com")) + await cookieStore.setCookie(.make(name: "name", value: "value", domain: "subdomain.duckduckgo.com")) - let dataStore = MockDataStore() - let cookieStore = MockHTTPCookieStore(cookies: [ - .make(domain: "duckduckgo.com"), - .make(domain: "subdomain.duckduckgo.com") - ]) - - dataStore.cookieStore = cookieStore + let storage = CookieStorage() + storage.isConsumed = true - let expect = expectation(description: #function) - WebCacheManager.shared.clear(logins: logins, dataStoreIdManager: dataStoreIdManager) { - expect.fulfill() - } - wait(for: [expect], timeout: 5.0) + await WebCacheManager.shared.clear(cookieStorage: storage, logins: logins, dataStoreIdManager: dataStoreIdManager) - XCTAssertEqual(cookieStore.cookies.count, 2) - XCTAssertTrue(cookieStore.cookies.contains(where: { $0.domain == "duckduckgo.com" })) - XCTAssertTrue(cookieStore.cookies.contains(where: { $0.domain == "subdomain.duckduckgo.com" })) + XCTAssertEqual(storage.cookies.count, 2) + XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "duckduckgo.com" })) + XCTAssertTrue(storage.cookies.contains(where: { $0.domain == "subdomain.duckduckgo.com" })) } @MainActor @@ -163,17 +166,18 @@ class WebCacheManagerTests: XCTestCase { let loadedCount = await defaultStore.httpCookieStore.allCookies().count XCTAssertEqual(2, loadedCount) - await withCheckedContinuation { continuation in - WebCacheManager.shared.clear(logins: logins, dataStoreIdManager: dataStoreIdManager) { - continuation.resume() - } - } + let cookieStore = CookieStorage() + + await WebCacheManager.shared.clear(cookieStorage: cookieStore, logins: logins, dataStoreIdManager: dataStoreIdManager) let cookies = await defaultStore.httpCookieStore.allCookies() - XCTAssertEqual(cookies.count, 1) - XCTAssertEqual(cookies[0].domain, "www.example.com") + XCTAssertEqual(cookies.count, 0) + + XCTAssertEqual(1, cookieStore.cookies.count) + XCTAssertEqual(cookieStore.cookies[0].domain, "www.example.com") } + @MainActor func testWhenAccessingObservationsDbThenValidDatabasePoolIsReturned() { let pool = WebCacheManager.shared.getValidDatabasePool() XCTAssertNotNil(pool, "DatabasePool should not be nil") @@ -181,23 +185,6 @@ class WebCacheManagerTests: XCTestCase { // MARK: Mocks - class MockDataStore: WebCacheManagerDataStore { - - func preservedCookies(_ preservedLogins: Core.PreserveLogins) async -> [HTTPCookie] { - [] - } - - var removeAllDataCalledCount = 0 - - var cookieStore: WebCacheManagerCookieStore? - - func legacyClearingRemovingAllDataExceptCookies(completion: @escaping () -> Void) { - removeAllDataCalledCount += 1 - completion() - } - - } - class MockPreservedLogins: PreserveLogins { let domains: [String] @@ -212,28 +199,4 @@ class WebCacheManagerTests: XCTestCase { } - class MockHTTPCookieStore: WebCacheManagerCookieStore { - - var cookies: [HTTPCookie] - - init(cookies: [HTTPCookie] = []) { - self.cookies = cookies - } - - func getAllCookies(_ completionHandler: @escaping ([HTTPCookie]) -> Void) { - completionHandler(cookies) - } - - func setCookie(_ cookie: HTTPCookie, completionHandler: (() -> Void)?) { - cookies.append(cookie) - completionHandler?() - } - - func delete(_ cookie: HTTPCookie, completionHandler: (() -> Void)?) { - cookies.removeAll { $0 == cookie } - completionHandler?() - } - - } - } From 30cc0836e8e8818d5dd1dd48cdeed3a8bd59bba6 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 19 Feb 2024 14:15:12 +0000 Subject: [PATCH 038/245] fix missing navigation bar from blank preview (#2487) --- DuckDuckGo/BlankSnapshotViewController.swift | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/DuckDuckGo/BlankSnapshotViewController.swift b/DuckDuckGo/BlankSnapshotViewController.swift index 5537036a85..b3e12628ec 100644 --- a/DuckDuckGo/BlankSnapshotViewController.swift +++ b/DuckDuckGo/BlankSnapshotViewController.swift @@ -110,6 +110,17 @@ class BlankSnapshotViewController: UIViewController { } private func configureOmniBar() { + viewCoordinator.navigationBarCollectionView.register(OmniBarCell.self, forCellWithReuseIdentifier: "omnibar") + viewCoordinator.navigationBarCollectionView.isPagingEnabled = true + + let layout = viewCoordinator.navigationBarCollectionView.collectionViewLayout as? UICollectionViewFlowLayout + layout?.scrollDirection = .horizontal + layout?.itemSize = CGSize(width: viewCoordinator.superview.frame.size.width, height: viewCoordinator.omniBar.frame.height) + layout?.minimumLineSpacing = 0 + layout?.minimumInteritemSpacing = 0 + layout?.scrollDirection = .horizontal + + viewCoordinator.navigationBarCollectionView.dataSource = self if AppWidthObserver.shared.isLargeWidth { viewCoordinator.omniBar.enterPadState() } @@ -126,6 +137,25 @@ class BlankSnapshotViewController: UIViewController { } } +extension BlankSnapshotViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return 1 + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "omnibar", for: indexPath) as? OmniBarCell else { + fatalError("Not \(OmniBarCell.self)") + } + cell.omniBar = viewCoordinator.omniBar + return cell + } + + func numberOfSections(in collectionView: UICollectionView) -> Int { + return 1 + } + +} + extension BlankSnapshotViewController: OmniBarDelegate { func onVoiceSearchPressed() { From 0b143975cdfd9e2fbdf3cb7bb90f36210ddd142d Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 19 Feb 2024 15:44:42 +0100 Subject: [PATCH 039/245] Release 7.109.0-1 (#2489) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++----------- fastlane/metadata/default/release_notes.txt | 1 + 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index baa472bee8..d20e8a04c7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8125,7 +8125,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8162,7 +8162,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8254,7 +8254,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8282,7 +8282,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8432,7 +8432,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8458,7 +8458,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8523,7 +8523,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8558,7 +8558,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8592,7 +8592,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8623,7 +8623,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8910,7 +8910,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8941,7 +8941,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8970,7 +8970,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9004,7 +9004,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9035,7 +9035,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9068,11 +9068,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9310,7 +9310,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9337,7 +9337,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9370,7 +9370,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9408,7 +9408,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9444,7 +9444,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9479,11 +9479,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9657,11 +9657,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9690,10 +9690,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index 6cd7a9e939..3e40ce3707 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,3 +1,4 @@ +- We fixed a bug preventing the widgets working on older versions of iOS - Bug fixes and other improvements. Join our fully distributed team and help raise the standard of trust online! https://duckduckgo.com/hiring From 3da1d389b8ec114d03face62a33d6bb9a34a2951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Mon, 19 Feb 2024 15:44:50 +0100 Subject: [PATCH 040/245] Try fix release.yml workflow file (#2488) --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f61b8358d8..c23a800196 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,7 +93,7 @@ jobs: else echo "::error::Asana Task URL has incorrect format (attempted to match ${task_url_regex})." fi - + - name: Upload debug symbols to Asana if: ${{ always() && github.event.inputs.asana-task-url }} env: @@ -108,7 +108,7 @@ jobs: --form "file=@${asana_dsyms_path};type=application/zip" fi - - name: Send Mattermost message + - name: Send Mattermost message if: ${{ success() || failure() }} # Don't execute when cancelled env: WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }} From e12fca830d759f232b5b4d173c159fcaad944635 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Mon, 19 Feb 2024 21:55:17 +0100 Subject: [PATCH 041/245] Update of the subscription entitlements API (#2486) Task/Issue URL: https://app.asana.com/0/414235014887631/1206633753419160/f Description: Update of the subscription entitlements API --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- DuckDuckGo/SettingsViewModel.swift | 8 +++----- DuckDuckGo/SubscriptionDebugViewController.swift | 12 +++++++----- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d20e8a04c7..adf7e453d4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9900,7 +9900,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 108.1.0; + version = 109.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 4db92a9200..b6945916ac 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "ab03bde3e1817b267debe9858a08b3f0caf72dc3", - "version" : "108.1.0" + "revision" : "5ecf4fe56f334be6eaecb65f6d55632a6d53921c", + "version" : "109.0.0" } }, { @@ -156,7 +156,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 0e79e5475e..2d937f2d9d 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -75,9 +75,6 @@ final class SettingsViewModel: ObservableObject { @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false - // Subscription Entitlement names: TBD - static let entitlementNames = ["dummy1", "dummy2", "dummy3"] - // Our View State @Published private(set) var state: SettingsState @@ -342,8 +339,9 @@ extension SettingsViewModel { } // Check for valid entitlements - let hasEntitlements = await AccountManager().hasEntitlement(for: Self.entitlementNames.first!) - self.state.subscription.hasActiveSubscription = hasEntitlements ? true : false + if case let .success(entitlements) = await AccountManager().fetchEntitlements() { + self.state.subscription.hasActiveSubscription = !entitlements.isEmpty + } // Cache Subscription state Self.cachedHasActiveSubscription = self.state.subscription.hasActiveSubscription diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 8e571649f7..feb61f2310 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -238,11 +238,13 @@ final class SubscriptionDebugViewController: UITableViewController { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } - for entitlementName in ["fake", "dummy1", "dummy2", "dummy3"] { - let result = await AccountManager().hasEntitlement(for: entitlementName) - let resultSummary = "Entitlement check for \(entitlementName): \(result)" - results.append(resultSummary) - print(resultSummary) + let entitlements: [AccountManager.Entitlement] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] + for entitlement in entitlements { + if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { + let resultSummary = "Entitlement check for \(entitlement.rawValue): \(result)" + results.append(resultSummary) + print(resultSummary) + } } showAlert(title: "Available Entitlements", message: results.joined(separator: "\n")) } diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index c74e599728..2ca38117ef 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 7032a49924..ada2c28cc0 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 9a9e0358d0..16cd15e42d 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "108.1.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 0222e3e7cfb6bd12f287ebad3863af98a8145173 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 19 Feb 2024 22:04:41 +0100 Subject: [PATCH 042/245] Subscription ITP Fixes (#2480) Task/Issue URL: https://app.asana.com/0/1204099484721401/1206585463538617/f Description: Adds allowedDomains setting to Webviews to prevent the user from loading pages outside of the scope Opens External Iris links in a separate Sheet Adds privacy protection and content blocking rules to Webviews Minor UI glitches and fixes Enabled links from Subscription Welcome page to ITR and PIR --- DuckDuckGo.xcodeproj/project.pbxproj | 10 +- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/SettingsSubscriptionView.swift | 18 ++++ DuckDuckGo/SettingsViewModel.swift | 1 + .../AsyncHeadlessWebView.swift | 11 ++- .../AsyncHeadlessWebViewModel.swift | 5 +- .../HeadlessWebView.swift | 29 +++++- .../HeadlessWebViewCoordinator.swift | 55 +++++++---- ...scriptionPagesUseSubscriptionFeature.swift | 2 +- .../SubscriptionEmailViewModel.swift | 10 +- .../SubscriptionExternalLinkViewModel.swift | 68 +++++++++++++ .../ViewModel/SubscriptionFlowViewModel.swift | 12 ++- .../ViewModel/SubscriptionITPViewModel.swift | 61 ++++++++++-- .../Views/SubscriptionExternalLinkView.swift | 96 +++++++++++++++++++ .../Views/SubscriptionITPView.swift | 17 +++- 15 files changed, 359 insertions(+), 38 deletions(-) create mode 100644 DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift create mode 100644 DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index adf7e453d4..af2cdfa410 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -787,6 +787,8 @@ D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; }; D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */; }; D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92C2B696945008E2FF2 /* Subscription.swift */; }; + D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; + D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */; }; @@ -2436,6 +2438,8 @@ D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesFeature.swift; sourceTree = ""; }; D668D92C2B696945008E2FF2 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; + D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; + D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TopMostController.swift"; sourceTree = ""; }; @@ -4512,6 +4516,7 @@ D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */, + D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -4533,6 +4538,7 @@ D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */, D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */, D668D9242B693778008E2FF2 /* SubscriptionITPView.swift */, + D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */, D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */, D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */, @@ -4545,9 +4551,9 @@ children = ( D668D92C2B696945008E2FF2 /* Subscription.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, + D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */, - D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, ); path = UserScripts; sourceTree = ""; @@ -6618,6 +6624,7 @@ 31CB4251273AF50700FA0F3F /* SpeechRecognizerProtocol.swift in Sources */, 319A37172829C8AD0079FBCE /* UITableViewExtension.swift in Sources */, 85EE7F59224673C5000FE757 /* WebContainerNavigationController.swift in Sources */, + D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */, F4C9FBF528340DDA002281CC /* AutofillInterfaceEmailTruncator.swift in Sources */, 1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */, 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */, @@ -6645,6 +6652,7 @@ D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */, D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, + D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */, BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index b6945916ac..18d1a22d06 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -12,7 +12,7 @@ { "identity" : "browserserviceskit", "kind" : "remoteSourceControl", - "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", + "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { "revision" : "5ecf4fe56f334be6eaecb65f6d55632a6d53921c", "version" : "109.0.0" diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 02b5acda25..3d4f3253ad 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -113,6 +113,24 @@ struct SettingsSubscriptionView: View { } }) + .onChange(of: viewModel.shouldNavigateToDBP, perform: { value in + if value { + // Allow the sheet to dismiss before presenting a new one + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + isShowingDBP = true + } + } + }) + + .onChange(of: viewModel.shouldNavigateToITP, perform: { value in + if value { + // Allow the sheet to dismiss before presenting a new one + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + isShowingITP = true + } + } + }) + .onReceive(subscriptionFlowViewModel.$selectedFeature) { value in guard let value else { return } viewModel.onAppearNavigationTarget = value diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 2d937f2d9d..1b84d3a200 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -97,6 +97,7 @@ final class SettingsViewModel: ObservableObject { enum SettingsSection: String { case none, netP, dbp, itr } + @Published var onAppearNavigationTarget: SettingsSection // MARK: Bindings diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift index 74b5224f9d..aa6a8fdbce 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift @@ -26,9 +26,18 @@ import Core struct AsyncHeadlessWebViewSettings { let bounces: Bool + let javascriptEnabled: Bool + let allowedDomains: [String]? + let contentBlocking: Bool - init(bounces: Bool = false) { + init(bounces: Bool = true, + javascriptEnabled: Bool = true, + allowedDomains: [String]? = nil, + contentBlocking: Bool = true) { self.bounces = bounces + self.javascriptEnabled = javascriptEnabled + self.allowedDomains = allowedDomains + self.contentBlocking = contentBlocking } } diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift index 304908891c..ef5b1878fe 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift @@ -37,10 +37,13 @@ final class AsyncHeadlessWebViewViewModel: ObservableObject { @Published var canGoBack: Bool = false @Published var canGoForward: Bool = false @Published var contentType: String = "" + @Published var allowedDomains: [String]? var navigationCoordinator = HeadlessWebViewNavCoordinator(webView: nil) - init(userScript: UserScriptMessaging?, subFeature: Subfeature?, settings: AsyncHeadlessWebViewSettings) { + init(userScript: UserScriptMessaging? = nil, + subFeature: Subfeature? = nil, + settings: AsyncHeadlessWebViewSettings) { self.userScript = userScript self.subFeature = subFeature self.settings = settings diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift index 5aa39ae262..a0e2acd8c6 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift @@ -21,6 +21,7 @@ import Foundation import SwiftUI import WebKit import UserScript +import BrowserServicesKit struct HeadlessWebView: UIViewRepresentable { let userScript: UserScriptMessaging? @@ -33,18 +34,24 @@ struct HeadlessWebView: UIViewRepresentable { var onContentType: ((String) -> Void)? var navigationCoordinator: HeadlessWebViewNavCoordinator - func makeUIView(context: Context) -> WKWebView { let configuration = WKWebViewConfiguration() configuration.userContentController = makeUserContentController() - let webView = WKWebView(frame: .zero, configuration: configuration) + let preferences = WKWebpagePreferences() + preferences.allowsContentJavaScript = settings.javascriptEnabled + preferences.preferredContentMode = .mobile + configuration.defaultWebpagePreferences = preferences - navigationCoordinator.webView = webView + let webView = WKWebView(frame: .zero, configuration: configuration) webView.uiDelegate = context.coordinator webView.scrollView.delegate = context.coordinator webView.scrollView.bounces = settings.bounces + webView.scrollView.contentInsetAdjustmentBehavior = .never webView.navigationDelegate = context.coordinator + webView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + navigationCoordinator.webView = webView + #if DEBUG if #available(iOS 16.4, *) { @@ -64,16 +71,30 @@ struct HeadlessWebView: UIViewRepresentable { onURLChange: onURLChange, onCanGoBack: onCanGoBack, onCanGoForward: onCanGoForward, - onContentType: onContentType) + onContentType: onContentType, + settings: settings + ) } @MainActor private func makeUserContentController() -> WKUserContentController { let userContentController = WKUserContentController() + + // Enable content blocking rules + if settings.contentBlocking { + let sourceProvider = DefaultScriptSourceProvider() + let contentBlockerUserScript = ContentBlockerRulesUserScript(configuration: sourceProvider.contentBlockerRulesConfig) + let contentScopeUserScript = ContentScopeUserScript(sourceProvider.privacyConfigurationManager, + properties: sourceProvider.contentScopeProperties) + userContentController.addUserScript(contentBlockerUserScript.makeWKUserScriptSync()) + userContentController.addUserScript(contentScopeUserScript.makeWKUserScriptSync()) + } + if let userScript, let subFeature { userContentController.addUserScript(userScript.makeWKUserScriptSync()) userContentController.addHandler(userScript) userScript.registerSubfeature(delegate: subFeature) + } return userContentController } diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift index a2fb375dc7..dd14a819ee 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift @@ -27,6 +27,9 @@ final class HeadlessWebViewCoordinator: NSObject { var onCanGoBack: ((Bool) -> Void)? var onCanGoForward: ((Bool) -> Void)? var onContentType: ((String) -> Void)? + var settings: AsyncHeadlessWebViewSettings + + var size: CGSize = .zero private var lastURL: URL? @@ -44,13 +47,16 @@ final class HeadlessWebViewCoordinator: NSObject { onURLChange: ((URL) -> Void)?, onCanGoBack: ((Bool) -> Void)?, onCanGoForward: ((Bool) -> Void)?, - onContentType: ((String) -> Void)?) { + onContentType: ((String) -> Void)?, + allowedDomains: [String]? = nil, + settings: AsyncHeadlessWebViewSettings = AsyncHeadlessWebViewSettings()) { self.parent = parent self.onScroll = onScroll self.onURLChange = onURLChange self.onCanGoBack = onCanGoBack self.onCanGoForward = onCanGoForward self.onContentType = onContentType + self.settings = settings } func setupWebViewObservation(_ webView: WKWebView) { @@ -107,32 +113,49 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - webView.evaluateJavaScript(Constants.contentTypeJS) { result, error in - guard error == nil, let contentType = result as? String else { - return + if settings.javascriptEnabled { + webView.evaluateJavaScript(Constants.contentTypeJS) { result, error in + guard error == nil, let contentType = result as? String else { + return + } + self.onContentType?(contentType) } - self.onContentType?(contentType) } } func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - guard let url = navigationAction.request.url else { - - decisionHandler(.allow) - return + guard let url = navigationAction.request.url, let scheme = url.scheme else { + decisionHandler(.cancel) + return } - - guard let scheme = url.scheme else { + + // Handle custom schemes (e.g., tel:, facetime:, etc.) + if Constants.externalSchemes.contains(scheme), UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:], completionHandler: nil) decisionHandler(.cancel) return } + + // Publish the URL change + self.onURLChange?(url) + lastURL = url - if Constants.externalSchemes.contains(scheme) && UIApplication.shared.canOpenURL(url) { - UIApplication.shared.open(url, options: [:], completionHandler: nil) - decisionHandler(.cancel) - } else { - decisionHandler(.allow) + // Validate the URL against allowed domains list, if present + if let allowedDomains = settings.allowedDomains, !allowedDomains.isEmpty { + let isURLAllowed = allowedDomains.contains { domain in + url.isPart(ofDomain: domain) + } + + decisionHandler(isURLAllowed ? .allow : .cancel) + return } + + // Default policy: allow navigation + decisionHandler(.allow) + } + + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { + // NOOP } } diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 90b2eacc49..dbfef2b806 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -266,7 +266,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec await withTransactionInProgress { transactionStatus = .restoring switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { - case .success(let update): + case .success: return true case .failure: return false diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 0a492e5e25..2bcb373c9e 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -39,6 +39,12 @@ final class SubscriptionEmailViewModel: ObservableObject { @Published var managingSubscriptionEmail = false @Published var webViewModel: AsyncHeadlessWebViewViewModel + private static let allowedDomains = [ + "duckduckgo.com", + "microsoftonline.com", + "duosecurity.com", + ] + private var cancellables = Set() init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), @@ -49,7 +55,9 @@ final class SubscriptionEmailViewModel: ObservableObject { self.accountManager = accountManager self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, subFeature: subFeature, - settings: AsyncHeadlessWebViewSettings(bounces: false)) + settings: AsyncHeadlessWebViewSettings(bounces: false, + allowedDomains: Self.allowedDomains, + contentBlocking: false)) initializeView() setupTransactionObservers() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift new file mode 100644 index 0000000000..391b430874 --- /dev/null +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift @@ -0,0 +1,68 @@ +// +// SubscriptionExternalLinkViewModel.swift +// DuckDuckGo +// +// 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 Core +import Combine + +#if SUBSCRIPTION +@available(iOS 15.0, *) +final class SubscriptionExternalLinkViewModel: ObservableObject { + + var url: URL + var allowedDomains: [String]? + private var canGoBackCancellable: AnyCancellable? + + @Published var webViewModel: AsyncHeadlessWebViewViewModel + @Published var canNavigateBack: Bool = false + + private var cancellables = Set() + + init(url: URL, allowedDomains: [String]? = nil) { + let webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, + allowedDomains: allowedDomains, + contentBlocking: true) + + self.url = url + self.webViewModel = AsyncHeadlessWebViewViewModel(settings: webViewSettings) + } + + // Observe transaction status + private func setupSubscribers() async { + + canGoBackCancellable = webViewModel.$canGoBack + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.canNavigateBack = value + } + } + + func initializeView() { + Task { await setupSubscribers() } + webViewModel.navigationCoordinator.navigateTo(url: url) + + } + + @MainActor + func navigateBack() async { + await webViewModel.navigationCoordinator.goBack() + } + +} +#endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index d42dbb0e61..f8b1cc0220 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -58,6 +58,16 @@ final class SubscriptionFlowViewModel: ObservableObject { @Published var shouldShowNavigationBar: Bool = false @Published var selectedFeature: SettingsViewModel.SettingsSection? @Published var canNavigateBack: Bool = false + + private static let allowedDomains = [ + "duckduckgo.com", + "microsoftonline.com", + "duosecurity.com", + ] + + private var webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, + allowedDomains: allowedDomains, + contentBlocking: false) init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), @@ -69,7 +79,7 @@ final class SubscriptionFlowViewModel: ObservableObject { self.selectedFeature = selectedFeature self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, subFeature: subFeature, - settings: AsyncHeadlessWebViewSettings(bounces: false)) + settings: webViewSettings) } // Observe transaction status diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index d189f1a6f1..0ff8125eb8 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -30,11 +30,13 @@ final class SubscriptionITPViewModel: ObservableObject { let userScript: IdentityTheftRestorationPagesUserScript let subFeature: IdentityTheftRestorationPagesFeature var manageITPURL = URL.identityTheftRestoration - var viewTitle = UserText.settingsPProITRTitle + var viewTitle = UserText.subscriptionTitle enum Constants { - static let navigationBarHideThreshold = 40.0 + static let navigationBarHideThreshold = 60.0 static let downloadableContent = ["application/pdf"] + static let blankURL = "about:blank" + static let externalSchemes = ["tel", "sms", "facetime"] } // State variables @@ -45,8 +47,23 @@ final class SubscriptionITPViewModel: ObservableObject { @Published var isDownloadableContent: Bool = false @Published var activityItems: [Any] = [] @Published var attachmentURL: URL? + + @Published var shouldNavigateToExternalURL: URL? + var shouldShowExternalURLSheet: Bool { + shouldNavigateToExternalURL != nil + } + private var currentURL: URL? + private static let allowedDomains = [ + "duckduckgo.com", + "microsoftonline.com", + "duosecurity.com", + ] + private var externalLinksViewModel: SubscriptionExternalLinkViewModel? + // Limit navigation to these external domains + private var externalAllowedDomains = ["irisidentityprotection.com"] + private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? @@ -54,9 +71,14 @@ final class SubscriptionITPViewModel: ObservableObject { subFeature: IdentityTheftRestorationPagesFeature = IdentityTheftRestorationPagesFeature()) { self.userScript = userScript self.subFeature = subFeature + + let webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, + allowedDomains: Self.allowedDomains, + contentBlocking: false) + self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, subFeature: subFeature, - settings: AsyncHeadlessWebViewSettings(bounces: false)) + settings: webViewSettings) } // Observe transaction status @@ -64,8 +86,9 @@ final class SubscriptionITPViewModel: ObservableObject { webViewModel.$scrollPosition .receive(on: DispatchQueue.main) + .throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true) .sink { [weak self] value in - self?.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold + self?.shouldShowNavigationBar = (value.y > Constants.navigationBarHideThreshold) } .store(in: &cancellables) @@ -91,13 +114,25 @@ final class SubscriptionITPViewModel: ObservableObject { webViewModel.$url .receive(on: DispatchQueue.main) - .sink { [weak self] value in - self?.isDownloadableContent = false - self?.currentURL = value + .sink { [weak self] url in + guard let self = self, let url = url else { return } + + // Check if allowedDomains is empty or if the URL is valid or part of the allowed domains + if Self.allowedDomains.isEmpty || + Self.allowedDomains.contains(where: { url.isPart(ofDomain: $0) }), + self.shouldNavigateToExternalURL == nil { + self.isDownloadableContent = false + self.currentURL = url + } else { + // Fire up navigation in a separate View (if a valid link) + if url.absoluteString != Constants.blankURL && + !Constants.externalSchemes.contains(url.scheme ?? "") { + self.shouldNavigateToExternalURL = url + } + } } .store(in: &cancellables) - canGoBackCancellable = webViewModel.$canGoBack .receive(on: DispatchQueue.main) .sink { [weak self] value in @@ -128,6 +163,16 @@ final class SubscriptionITPViewModel: ObservableObject { } } } + + func getExternalLinksViewModel(url: URL) -> SubscriptionExternalLinkViewModel { + if let existingModel = externalLinksViewModel { + return existingModel + } else { + let model = SubscriptionExternalLinkViewModel(url: url, allowedDomains: externalAllowedDomains) + externalLinksViewModel = model + return model + } + } @MainActor diff --git a/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift b/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift new file mode 100644 index 0000000000..badb1932bd --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift @@ -0,0 +1,96 @@ +// +// SubscriptionExternalLinkView.swift +// DuckDuckGo +// +// Copyright © 2023 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. +// + +#if SUBSCRIPTION +import SwiftUI +import Foundation +import DesignResourcesKit + +@available(iOS 15.0, *) +struct SubscriptionExternalLinkView: View { + + @Environment(\.dismiss) var dismiss + @ObservedObject var viewModel: SubscriptionExternalLinkViewModel + + enum Constants { + static let navButtonPadding: CGFloat = 20.0 + static let backButtonImage = "chevron.left" + } + + + var body: some View { + NavigationView { + baseView + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + backButton + } + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.subscriptionCloseButton) { dismiss() } + } + } + .navigationBarTitleDisplayMode(.inline) + .navigationViewStyle(.stack) + + .onAppear(perform: { + setUpAppearances() + viewModel.initializeView() + }) + }.tint(Color(designSystemColor: .textPrimary)) + } + + private var baseView: some View { + ZStack(alignment: .top) { + webView + } + } + + @ViewBuilder + private var webView: some View { + + ZStack(alignment: .top) { + AsyncHeadlessWebView(viewModel: viewModel.webViewModel) + .background() + } + } + + @ViewBuilder + private var backButton: some View { + if viewModel.canNavigateBack { + Button(action: { + Task { await viewModel.navigateBack() } + }, label: { + HStack(spacing: 0) { + Image(systemName: Constants.backButtonImage) + Text(UserText.backButtonTitle) + } + }) + } + } + + + private func setUpAppearances() { + let navAppearance = UINavigationBar.appearance() + navAppearance.backgroundColor = UIColor(designSystemColor: .surface) + navAppearance.barTintColor = UIColor(designSystemColor: .surface) + navAppearance.shadowImage = UIImage() + navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) + } +} +#endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 7d8c83c7b6..6111e75246 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -73,15 +73,26 @@ struct SubscriptionITPView: View { Button(UserText.subscriptionCloseButton) { dismiss() } } } - .edgesIgnoringSafeArea(.top) + .edgesIgnoringSafeArea(.all) .navigationBarTitleDisplayMode(.inline) - .navigationBarHidden(!viewModel.shouldShowNavigationBar && !viewModel.isDownloadableContent).animation(.easeOut) + .navigationBarHidden(!viewModel.shouldShowNavigationBar && !viewModel.isDownloadableContent).animation(.snappy) .onAppear(perform: { setUpAppearances() viewModel.initializeView() }) - }.tint(Color(designSystemColor: .textPrimary)) + + } + .tint(Color(designSystemColor: .textPrimary)) + + .sheet(isPresented: Binding( + get: { viewModel.shouldShowExternalURLSheet }, + set: { if !$0 { viewModel.shouldNavigateToExternalURL = nil } } + )) { + if let url = viewModel.shouldNavigateToExternalURL { + SubscriptionExternalLinkView(viewModel: viewModel.getExternalLinksViewModel(url: url)) + } + } } private var baseView: some View { From 1c8537b7161b639a4d9b00aea9892bd043bf8e69 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 19 Feb 2024 22:26:37 +0100 Subject: [PATCH 043/245] Metadata update (#2490) Task/Issue URL: https://app.asana.com/0/1203642541599510/1206510531389113/f Tech Design URL: CC: Description: Updated metadata --- fastlane/metadata/en-CA/description.txt | 4 +--- fastlane/metadata/en-GB/description.txt | 4 +--- fastlane/metadata/en-US/description.txt | 4 +--- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/fastlane/metadata/en-CA/description.txt b/fastlane/metadata/en-CA/description.txt index cd8bcb0d2e..36d8d4e3dd 100644 --- a/fastlane/metadata/en-CA/description.txt +++ b/fastlane/metadata/en-CA/description.txt @@ -1,6 +1,4 @@ -The DuckDuckGo app provides the most comprehensive online privacy protection with the push of a button. With one free download, you get an everyday private Internet browser that offers seamless protection while you search and browse, and even access to tracking protection for emails you receive. - -Protecting your privacy online is like protecting your home. Locking the front door won’t stop the most determined folks from getting inside, especially if you’ve left the back door and windows unlocked and an extra key under the doormat. That’s why we offer multiple types of privacy protection, most of which aren’t offered in most popular browsers by default. +The DuckDuckGo browser provides the most comprehensive online privacy protection in one app. Unlike most popular browsers, it has powerful privacy protections by default, including our search engine that doesn’t track your history and over a dozen other built-in protections. Millions of people use DuckDuckGo as their go-to browser to protect their everyday online activities, from searching to browsing, emailing, and more.   FEATURE HIGHLIGHTS • Search Privately by Default - DuckDuckGo Private Search comes built-in, so you can easily search the web without being tracked. diff --git a/fastlane/metadata/en-GB/description.txt b/fastlane/metadata/en-GB/description.txt index 618b4332c9..c0444a66a2 100644 --- a/fastlane/metadata/en-GB/description.txt +++ b/fastlane/metadata/en-GB/description.txt @@ -1,6 +1,4 @@ -The DuckDuckGo app provides the most comprehensive online privacy protection with the push of a button. With one download, you get a new everyday browser that offers seamless protection while you search and browse, and even access to tracking protection for emails you receive. - -Protecting your privacy online is like protecting your home. Locking the front door won’t stop the most determined folks from getting inside, especially if you’ve left the back door and windows unlocked and an extra key under the doormat. That’s why we offer multiple types of privacy protection, most of which aren’t offered in most popular browsers by default. +The DuckDuckGo browser provides the most comprehensive online privacy protection in one app. Unlike most popular browsers, it has powerful privacy protections by default, including our search engine that doesn’t track your history and over a dozen other built-in protections. Millions of people use DuckDuckGo as their go-to browser to protect their everyday online activities, from searching to browsing, emailing, and more. FEATURE HIGHLIGHTS • Search Privately by Default — DuckDuckGo Private Search comes built-in, so you can easily search the web without being tracked. diff --git a/fastlane/metadata/en-US/description.txt b/fastlane/metadata/en-US/description.txt index 3c4ab95397..61bf4cae26 100644 --- a/fastlane/metadata/en-US/description.txt +++ b/fastlane/metadata/en-US/description.txt @@ -1,6 +1,4 @@ -The DuckDuckGo app provides the most comprehensive online privacy protection with the push of a button. With one free download, you get an everyday private Internet browser that offers seamless protection while you search and browse, and even access to tracking protection for emails you receive. - -Protecting your privacy online is like protecting your home. Locking the front door won’t stop the most determined folks from getting inside, especially if you’ve left the back door and windows unlocked and an extra key under the doormat. That’s why we offer multiple types of privacy protection, most of which aren’t offered in most popular browsers by default. +The DuckDuckGo browser provides the most comprehensive online privacy protection in one app. Unlike most popular browsers, it has powerful privacy protections by default, including our search engine that doesn’t track your history and over a dozen other built-in protections. Millions of people use DuckDuckGo as their go-to browser to protect their everyday online activities, from searching to browsing, emailing, and more.   FEATURE HIGHLIGHTS • Search Privately by Default - DuckDuckGo Private Search comes built-in, so you can easily search the web without being tracked. From ec05ac07d69bcd39362da9f48e2cd78ad3b34924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Tue, 20 Feb 2024 09:22:26 +0100 Subject: [PATCH 044/245] Update upstream packages (#2465) --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index af2cdfa410..ba48759ab4 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9900,7 +9900,7 @@ repositoryURL = "https://github.com/weichsel/ZIPFoundation.git"; requirement = { kind = exactVersion; - version = 0.9.17; + version = 0.9.18; }; }; 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */ = { @@ -9916,7 +9916,7 @@ repositoryURL = "https://github.com/scinfu/SwiftSoup"; requirement = { kind = exactVersion; - version = 2.4.2; + version = 2.7.0; }; }; F42D541B29DCA40B004C4FF1 /* XCRemoteSwiftPackageReference "DesignResourcesKit" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 18d1a22d06..8c6ed5092d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -140,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup", "state" : { - "revision" : "41e7c263fb8c277e980ebcb9b0b5f6031d3d4886", - "version" : "2.4.2" + "revision" : "f83c097597094a04124eb6e0d1e894d24129af87", + "version" : "2.7.0" } }, { @@ -176,8 +176,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/weichsel/ZIPFoundation.git", "state" : { - "revision" : "a3f5c2bae0f04b0bce9ef3c4ba6bd1031a0564c4", - "version" : "0.9.17" + "revision" : "b979e8b52c7ae7f3f39fa0182e738e9e7257eb78", + "version" : "0.9.18" } } ], From 889d1116526f1a144c8d04ce68fd13485af75b5f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 20 Feb 2024 17:40:04 +0100 Subject: [PATCH 045/245] Subscription Entitlement Checks (#2491) Task/Issue URL: https://app.asana.com/0/1204099484721401/1206427924871956/f Description: Adopts the final entitlements for subscriptions Updates to latest BSK Steps to test this PR: Get a subscription Confirm all three options are visible in settings --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/SettingsSubscriptionView.swift | 44 +++++++------- DuckDuckGo/SettingsViewModel.swift | 57 ++++++++++++------- 3 files changed, 61 insertions(+), 42 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8c6ed5092d..1b44f4907b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -156,7 +156,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 3d4f3253ad..d9e11e2acd 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -67,30 +67,36 @@ struct SettingsSubscriptionView: View { private var subscriptionDetailsView: some View { return Group { - SettingsCellView(label: UserText.settingsPProVPNTitle, - subtitle: viewModel.state.networkProtection.status != "" ? viewModel.state.networkProtection.status : nil, - action: { viewModel.presentLegacyView(.netP) }, - disclosureIndicator: true, - isButton: true) - + if viewModel.shouldShowNetP { + SettingsCellView(label: UserText.settingsPProVPNTitle, + subtitle: viewModel.state.networkProtection.status != "" ? viewModel.state.networkProtection.status : nil, + action: { viewModel.presentLegacyView(.netP) }, + disclosureIndicator: true, + isButton: true) + } - SettingsCellView(label: UserText.settingsPProDBPTitle, - subtitle: UserText.settingsPProDBPSubTitle, - action: { isShowingDBP.toggle() }, isButton: true) - .sheet(isPresented: $isShowingDBP) { - SubscriptionPIRView() + if viewModel.shouldShowDBP { + SettingsCellView(label: UserText.settingsPProDBPTitle, + subtitle: UserText.settingsPProDBPSubTitle, + action: { isShowingDBP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingDBP) { + SubscriptionPIRView() + } } - SettingsCellView(label: UserText.settingsPProITRTitle, - subtitle: UserText.settingsPProITRSubTitle, - action: { isShowingITP.toggle() }, isButton: true) - .sheet(isPresented: $isShowingITP) { - SubscriptionITPView() + if viewModel.shouldShowITP { + SettingsCellView(label: UserText.settingsPProITRTitle, + subtitle: UserText.settingsPProITRSubTitle, + action: { isShowingITP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingITP) { + SubscriptionITPView() + } } - - NavigationLink(destination: SubscriptionSettingsView()) { - SettingsCustomCell(content: { manageSubscriptionView }) + if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { + NavigationLink(destination: SubscriptionSettingsView()) { + SettingsCustomCell(content: { manageSubscriptionView }) + } } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 1b84d3a200..2064972949 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -74,6 +74,10 @@ final class SettingsViewModel: ObservableObject { // Add more views as needed here... @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false + + @Published var shouldShowNetP = false + @Published var shouldShowDBP = false + @Published var shouldShowITP = false // Our View State @Published private(set) var state: SettingsState @@ -327,32 +331,41 @@ extension SettingsViewModel { @available(iOS 15.0, *) @MainActor private func setupSubscriptionEnvironment() async { - // Active subscription check - if let token = accountManager.accessToken { + guard let token = accountManager.accessToken else { + setupSubscriptionPurchaseOptions() + return + } + + // Fetch available subscriptions from the backend (or sign out) + switch await SubscriptionService.getSubscriptionDetails(token: token) { + case .success(let response) where !response.isSubscriptionActive: + AccountManager().signOut() + setupSubscriptionPurchaseOptions() + case .success(let response): + // Cache Subscription state + Self.cachedHasActiveSubscription = self.state.subscription.hasActiveSubscription - // Fetch available subscriptions from the backend (or sign out) - if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { - if !response.isSubscriptionActive { - AccountManager().signOut() - setupSubscriptionPurchaseOptions() - return - } - - // Check for valid entitlements - if case let .success(entitlements) = await AccountManager().fetchEntitlements() { - self.state.subscription.hasActiveSubscription = !entitlements.isEmpty - } - - // Cache Subscription state - Self.cachedHasActiveSubscription = self.state.subscription.hasActiveSubscription - - // Enable Subscription purchase if there's no active subscription - if self.state.subscription.hasActiveSubscription == false { - setupSubscriptionPurchaseOptions() + // Check entitlements and update UI accordingly + let entitlements: [AccountManager.Entitlement] = [.identityTheftRestoration, .dataBrokerProtection, .networkProtection] + for entitlement in entitlements { + if case .success = await AccountManager().hasEntitlement(for: entitlement) { + switch entitlement { + case .identityTheftRestoration: + self.shouldShowITP = true + case .dataBrokerProtection: + self.shouldShowDBP = true + case .networkProtection: + self.shouldShowNetP = true + } } } - } else { + + // Enable Subscription purchase if there's no active subscription + if !self.state.subscription.hasActiveSubscription { + setupSubscriptionPurchaseOptions() + } + default: setupSubscriptionPurchaseOptions() } } From be3dd4d70165dfe3ca702549fcce69a418e6c468 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 21 Feb 2024 22:28:39 +0100 Subject: [PATCH 046/245] Updates BSK (#2429) Task/Issue URL: https://app.asana.com/0/0/1206462407536023/f Tech Design URL: - [Tech Design: How to exclude Data Broker traffic?](https://app.asana.com/0/481882893211075/1206363506060150/f) - [Tech Design: Mechanism to allow PIR to start excluding its traffic from the VPN tunnel](https://app.asana.com/0/481882893211075/1206446978081253/f) - [Tech Design: How will the proxy recover from failure?](https://app.asana.com/0/481882893211075/1206446978546262) macOS PR: https://github.com/duckduckgo/macos-browser/pull/2128 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/652 ## Description The changes from BSK shouldn't affect iOS. We're only making sure the latest BSK version is integrated. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ba48759ab4..7715d7999f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9908,7 +9908,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 109.0.0; + version = 109.0.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1b44f4907b..fbc1e8be0b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "5ecf4fe56f334be6eaecb65f6d55632a6d53921c", - "version" : "109.0.0" + "revision" : "da6a822844922401d80e26963b8b11dcd6ef221a", + "version" : "109.0.1" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 2ca38117ef..253907ae40 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index ada2c28cc0..d24848a4f9 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 16cd15e42d..f355cda2ae 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From fe78ca5d747444973a448fc76b7d43278b26c350 Mon Sep 17 00:00:00 2001 From: Brian Hall Date: Thu, 22 Feb 2024 12:08:47 -0600 Subject: [PATCH 047/245] Update BSK (#2483) Co-authored-by: Fernando Bunn --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7715d7999f..7d230d7092 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9908,7 +9908,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 109.0.1; + version = 109.0.2; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fbc1e8be0b..558cd1d5e9 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "da6a822844922401d80e26963b8b11dcd6ef221a", - "version" : "109.0.1" + "revision" : "da5f8ae73e7ad7fc47931f82f5ac6c4fafa6ac94", + "version" : "109.0.2" } }, { @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "063b560e59a50e03d9b00b88a7fcb2ed2b562395", - "version" : "4.61.0" + "revision" : "36ddba2cbac52a41b9a9275af06d32fa8a56d2d7", + "version" : "4.64.0" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 253907ae40..a0a35fc745 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.2"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index d24848a4f9..b921a69e4a 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.2"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index f355cda2ae..f871a9f6ba 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.2"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From a27e6d5124124f5a4dcbce7e632987a53ad49c77 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Thu, 22 Feb 2024 13:41:58 -0600 Subject: [PATCH 048/245] Add HTTP errors and status codes to site breakage reports (#2477) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../PrivacyDashboardViewController.swift | 15 +++++-- .../BrokenSiteReportingTests.swift | 39 ++++++++++++++++--- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- submodules/privacy-reference-tests | 2 +- 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 7d230d7092..a40314bcc0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9908,7 +9908,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 109.0.2; + version = 110.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 558cd1d5e9..47aa0fc996 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "da5f8ae73e7ad7fc47931f82f5ac6c4fafa6ac94", - "version" : "109.0.2" + "revision" : "d56b90bd229288f681f0a3a6a325ef25e3ce5f3c", + "version" : "110.0.0" } }, { diff --git a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift index 586c8e99d9..68521a29dd 100644 --- a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift @@ -237,7 +237,16 @@ extension PrivacyDashboardViewController { let blockedTrackerDomains = privacyInfo.trackerInfo.trackersBlocked.compactMap { $0.domain } let configuration = ContentBlocking.shared.privacyConfigurationManager.privacyConfig let protectionsState = configuration.isFeature(.contentBlocking, enabledForDomain: breakageAdditionalInfo.currentURL.host) - + + var errors: [Error]? + var statusCodes: [Int]? + if let error = breakageAdditionalInfo.error { + errors = [error] + } + if let httpStatusCode = breakageAdditionalInfo.httpStatusCode { + statusCodes = [httpStatusCode] + } + return WebsiteBreakage(siteUrl: breakageAdditionalInfo.currentURL, category: category, description: description, @@ -255,7 +264,7 @@ extension PrivacyDashboardViewController { siteType: breakageAdditionalInfo.isDesktop ? .desktop : .mobile, atb: StatisticsUserDefaults().atb ?? "", model: UIDevice.current.model, - error: breakageAdditionalInfo.error, - httpStatusCode: breakageAdditionalInfo.httpStatusCode) + errors: errors, + httpStatusCodes: statusCodes) } } diff --git a/DuckDuckGoTests/BrokenSiteReportingTests.swift b/DuckDuckGoTests/BrokenSiteReportingTests.swift index 838b3478bf..e449d5fe4b 100644 --- a/DuckDuckGoTests/BrokenSiteReportingTests.swift +++ b/DuckDuckGoTests/BrokenSiteReportingTests.swift @@ -38,6 +38,22 @@ final class BrokenSiteReportingTests: XCTestCase { static let tests = "privacy-reference-tests/broken-site-reporting/tests.json" } + struct MockError: LocalizedError { + let description: String + + init(_ description: String) { + self.description = description + } + + var errorDescription: String? { + description + } + + var localizedDescription: String? { + description + } + } + override func setUp() { super.setUp() @@ -73,10 +89,15 @@ final class BrokenSiteReportingTests: XCTestCase { } os_log("Testing [%s]", type: .info, test.name) - + + var errors: [Error]? + if let errs = test.errorDescriptions { + errors = errs.map { MockError($0) } + } + let websiteBreakage = WebsiteBreakage(siteUrl: URL(string: test.siteURL)!, category: test.category, - description: "", + description: test.providedDescription, osVersion: test.os ?? "", manufacturer: test.manufacturer ?? "", upgradedHttps: test.wasUpgraded, @@ -91,8 +112,8 @@ final class BrokenSiteReportingTests: XCTestCase { siteType: .mobile, atb: "", model: test.model ?? "", - error: nil, - httpStatusCode: nil) + errors: errors, + httpStatusCodes: test.httpErrorCodes ?? []) let reporter = WebsiteBreakageReporter(pixelHandler: { params in @@ -100,7 +121,12 @@ final class BrokenSiteReportingTests: XCTestCase { if let actualValue = params[expectedParam.name], let expectedCleanValue = expectedParam.value.removingPercentEncoding { - if actualValue != expectedCleanValue { + if expectedParam.name == "errorDescriptions" { + // `localizedDescription` includes class information. This format is likely to differ per platform + // anyway. So we'll just check if the value contains an array of strings + XCTAssert(actualValue.split(separator: ",").count > 1, + "Param \(expectedParam.name) expected to be an array of strings. Received: \(actualValue)") + } else if actualValue != expectedCleanValue { XCTFail("Mismatching param: \(expectedParam.name) => \(expectedCleanValue) != \(actualValue)") } } else { @@ -133,6 +159,7 @@ private struct Test: Codable { let siteURL: String let wasUpgraded: Bool let category: String + let providedDescription: String? let blockedTrackers, surrogates: [String] let atb, blocklistVersion: String let expectReportURLPrefix: String @@ -141,6 +168,8 @@ private struct Test: Codable { let manufacturer, model, os: String? let gpcEnabled: Bool? let protectionsEnabled: Bool + let errorDescriptions: [String]? + let httpErrorCodes: [Int]? } // MARK: - ExpectReportURLParam diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index a0a35fc745..fb9d911c3d 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index b921a69e4a..e94aa1baa5 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index f871a9f6ba..4886b2ffd4 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "109.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..40ce86837d 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit 40ce86837def0adbf558f00ed0531ab4df5839a8 From 77cace8dbc0aabef7ff289dc8cb3db07fe072458 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 23 Feb 2024 11:24:09 +0000 Subject: [PATCH 049/245] Fix flakey data clearing maestro test (#2493) --- .maestro/data_clearing_tests/01_fire_proofing.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.maestro/data_clearing_tests/01_fire_proofing.yml b/.maestro/data_clearing_tests/01_fire_proofing.yml index e717392439..706fabe76e 100644 --- a/.maestro/data_clearing_tests/01_fire_proofing.yml +++ b/.maestro/data_clearing_tests/01_fire_proofing.yml @@ -28,8 +28,8 @@ tags: - inputText: "TestName" - tapOn: "Cookie value" - inputText: "TestValue" -- scrollUntilVisible: - centerElement: true +- tapOn: "Done" +- scrollUntilVisible: element: text: "Submit" - tapOn: "Submit" From 2700cb9d6e0a55cc55c20c38fd2711aec1c087c0 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Fri, 23 Feb 2024 21:36:45 +0100 Subject: [PATCH 050/245] Fix on demand so that it's not enabled too soon (#2499) Task/Issue URL: https://app.asana.com/0/1203137811378537/1206543593200557/f ## Description: Enables on-demand only after the VPN has connected. --- .../NetworkProtectionTunnelController.swift | 42 ++++++++++++++++--- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index 595b0afa13..77a09a12e1 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -31,6 +31,9 @@ final class NetworkProtectionTunnelController: TunnelController { private let debugFeatures = NetworkProtectionDebugFeatures() private let tokenStore = NetworkProtectionKeychainTokenStore() private let errorStore = NetworkProtectionTunnelErrorStore() + private let notificationCenter: NotificationCenter = .default + private var previousStatus: NEVPNStatus = .invalid + private var cancellables = Set() // MARK: - Starting & Stopping the VPN @@ -39,6 +42,10 @@ final class NetworkProtectionTunnelController: TunnelController { case simulateControllerFailureError } + init() { + subscribeToStatusChanges() + } + /// Starts the VPN connection used for Network Protection /// func start() async { @@ -142,12 +149,6 @@ final class NetworkProtectionTunnelController: TunnelController { Pixel.fire(pixel: .networkProtectionActivationRequestFailed, error: error) throw error } - - if !debugFeatures.alwaysOnDisabled { - Task { - try await enableOnDemand(tunnelManager: tunnelManager) - } - } } /// The actual storage for our tunnel manager. @@ -231,6 +232,35 @@ final class NetworkProtectionTunnelController: TunnelController { tunnelManager.onDemandRules = [NEOnDemandRuleConnect()] } + // MARK: - Observing Status Changes + + private func subscribeToStatusChanges() { + notificationCenter.publisher(for: .NEVPNStatusDidChange) + .sink(receiveValue: handleStatusChange(_:)) + .store(in: &cancellables) + } + + private func handleStatusChange(_ notification: Notification) { + guard !debugFeatures.alwaysOnDisabled, + let session = (notification.object as? NETunnelProviderSession), + session.status != previousStatus, + let manager = session.manager as? NETunnelProviderManager else { + return + } + + Task { @MainActor in + previousStatus = session.status + + switch session.status { + case .connected: + try await enableOnDemand(tunnelManager: manager) + default: + break + } + + } + } + // MARK: - On Demand @MainActor From 592a7baac15fd68fa9ee917eab68af1920d3eb20 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Sat, 24 Feb 2024 02:03:17 +0100 Subject: [PATCH 051/245] Fix some issues with handling when the adapter is started up (#2496) Task/Issue URL: https://app.asana.com/0/0/1206634919441353/f macOS PR: https://github.com/duckduckgo/macos-browser/pull/2251 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/674 ## Description Fixes how we handle the event of the tunnel adapter started. More broadly, this PR tries to ensure we don't "histerically" start and stop our monitors, since that could result in a lot of noise in our metrics. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- .../NetworkProtectionPacketTunnelProvider.swift | 14 +++++--------- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a40314bcc0..bf9f519684 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9908,7 +9908,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 110.0.0; + version = 110.0.1; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 47aa0fc996..fc790ff80e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -12,10 +12,10 @@ { "identity" : "browserserviceskit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BrowserServicesKit", + "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "d56b90bd229288f681f0a3a6a325ef25e3ce5f3c", - "version" : "110.0.0" + "revision" : "483427db845410f10121cf2200f5a940c9bbf70b", + "version" : "110.0.1" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index fb9d911c3d..3833fc5480 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index e94aa1baa5..702ea615d2 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 4886b2ffd4..42616e968a 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index f127d65a7d..dc5ddd2d44 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -221,7 +221,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { settings: settings) startMonitoringMemoryPressureEvents() observeServerChanges() - observeStatusChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) } @@ -259,14 +258,11 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { private let activationDateStore = DefaultVPNWaitlistActivationDateStore() - private func observeStatusChanges() { - connectionStatusPublisher.sink { [weak self] status in - if case .connected = status { - self?.activationDateStore.setActivationDateIfNecessary() - self?.activationDateStore.updateLastActiveDate() - } - } - .store(in: &cancellables) + public override func handleConnectionStatusChange(old: ConnectionStatus, new: ConnectionStatus) { + super.handleConnectionStatusChange(old: old, new: new) + + activationDateStore.setActivationDateIfNecessary() + activationDateStore.updateLastActiveDate() } } From b835880a313b6dc2d22966ea34df94c771eeab3e Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Mon, 26 Feb 2024 09:00:11 +0000 Subject: [PATCH 052/245] Fcappelli/skan4 (#2474) Task/Issue URL: https://app.asana.com/0/414235014887631/1206011173132781/f SKAD 4 postback support --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +++ DuckDuckGo/AppDelegate+SKAD4.swift | 38 ++++++++++++++++++++++++++++ DuckDuckGo/AppDelegate.swift | 3 +++ 3 files changed, 45 insertions(+) create mode 100644 DuckDuckGo/AppDelegate+SKAD4.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bf9f519684..e55285ae5d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -895,6 +895,7 @@ F143C3281E4A9A0E00CFDE3A /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F143C3241E4A9A0E00CFDE3A /* StringExtension.swift */; }; F143C3291E4A9A0E00CFDE3A /* URLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F143C3251E4A9A0E00CFDE3A /* URLExtension.swift */; }; F14E491F1E391CE900DC037C /* URLExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F14E491E1E391CE900DC037C /* URLExtensionTests.swift */; }; + F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */; }; F159BDA41F0BDB5A00B4A01D /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */; }; F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; @@ -2574,6 +2575,7 @@ F143C32C1E4A9A4800CFDE3A /* UIViewControllerExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIViewControllerExtension.swift; path = ../Core/UIViewControllerExtension.swift; sourceTree = ""; }; F143C3451E4AA32D00CFDE3A /* SearchBarExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SearchBarExtension.swift; path = ../Core/SearchBarExtension.swift; sourceTree = ""; }; F14E491E1E391CE900DC037C /* URLExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLExtensionTests.swift; sourceTree = ""; }; + F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "AppDelegate+SKAD4.swift"; sourceTree = ""; }; F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabViewController.swift; sourceTree = ""; }; F15D431F1E706CC500BF2CDC /* AutocompleteViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewController.swift; sourceTree = ""; }; F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherViewController.swift; sourceTree = ""; }; @@ -5316,6 +5318,7 @@ 84E341951E2F7EFB00BDBA6F /* AppDelegate.swift */, 85DB12EC2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift */, 4BCD14682B05BDD5000B1E4C /* AppDelegate+Waitlists.swift */, + F1564F022B7B915F00D454A6 /* AppDelegate+SKAD4.swift */, 98B31291218CCB8C00E54DE1 /* AppDependencyProvider.swift */, 85BA58591F3506AE00C6E8CA /* AppSettings.swift */, 85BA58541F34F49E00C6E8CA /* AppUserDefaults.swift */, @@ -6519,6 +6522,7 @@ 858650D12469BCDE00C36F8A /* DaxDialogs.swift in Sources */, 310D091B2799F54900DC0060 /* DownloadManager.swift in Sources */, 98D98A7425ED88D100D8E3DF /* BrowsingMenuEntryViewCell.swift in Sources */, + F1564F032B7B915F00D454A6 /* AppDelegate+SKAD4.swift in Sources */, 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */, CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, 85F200002215C17B006BB258 /* FindInPage.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate+SKAD4.swift b/DuckDuckGo/AppDelegate+SKAD4.swift new file mode 100644 index 0000000000..672fd18ced --- /dev/null +++ b/DuckDuckGo/AppDelegate+SKAD4.swift @@ -0,0 +1,38 @@ +// +// AppDelegate+SKAD4.swift +// DuckDuckGo +// +// Copyright © 2023 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 StoreKit +import Common + +extension AppDelegate { + + func updateSKAd(conversionValue: Int) { + + if #available(iOS 16.1, *) { + SKAdNetwork.updatePostbackConversionValue(conversionValue, coarseValue: .high, lockWindow: true, completionHandler: { error in + if let error = error { + os_log("SKAD 4 postback failed %@", type: .error, error.localizedDescription) + } + }) + } else { + os_log("SKAD 4 Not supported in this iOS version", type: .debug) + } + } +} diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 8e81eb22af..01bcadace1 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -94,6 +94,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // swiftlint:disable:next function_body_length cyclomatic_complexity func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // SKAD4 support + updateSKAd(conversionValue: 1) + #if targetEnvironment(simulator) if ProcessInfo.processInfo.environment["UITESTING"] == "true" { // Disable hardware keyboards. From 09c7af0ec4bd589514260b0bc8e2aa97913553e5 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 26 Feb 2024 11:16:36 +0000 Subject: [PATCH 053/245] add history to variant manager and privacy config (#2495) --- Core/DefaultVariantManager.swift | 4 +- Core/FeatureFlag.swift | 3 + Core/HistoryManager.swift | 37 +++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 26 +++++++- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGoTests/HistoryManagerTests.swift | 66 +++++++++++++++++++ LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 9 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 Core/HistoryManager.swift create mode 100644 DuckDuckGoTests/HistoryManagerTests.swift diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift index 29b3d5b46f..3072c39ab7 100644 --- a/Core/DefaultVariantManager.swift +++ b/Core/DefaultVariantManager.swift @@ -26,8 +26,8 @@ extension FeatureName { // Define your feature e.g.: // public static let experimentalFeature = FeatureName(rawValue: "experimentalFeature") - public static let fixedUserAgent = FeatureName(rawValue: "fixedUserAgent") - public static let closestUserAgent = FeatureName(rawValue: "closestUserAgent") + // Experiment coming soon + public static let history = FeatureName(rawValue: "history") } public struct VariantIOS: Variant { diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 71bd3176ef..1246732321 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -37,6 +37,7 @@ public enum FeatureFlag: String { case subscription case swipeTabs case autoconsentOnByDefault + case history } extension FeatureFlag: FeatureFlagSourceProviding { @@ -68,6 +69,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.feature(.incontextSignup)) case .autoconsentOnByDefault: return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault)) + case .history: + return .remoteReleasable(.feature(.history)) } } } diff --git a/Core/HistoryManager.swift b/Core/HistoryManager.swift new file mode 100644 index 0000000000..957a8d327e --- /dev/null +++ b/Core/HistoryManager.swift @@ -0,0 +1,37 @@ +// +// HistoryManager.swift +// DuckDuckGo +// +// 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 + +class HistoryManager { + + let privacyConfig: PrivacyConfiguration + let variantManager: VariantManager + + init(privacyConfig: PrivacyConfiguration, variantManager: VariantManager) { + self.privacyConfig = privacyConfig + self.variantManager = variantManager + } + + func isHistoryFeatureEnabled() -> Bool { + return privacyConfig.isEnabled(featureKey: .history) && variantManager.isSupported(feature: .history) + } + +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e55285ae5d..fd8ea42b50 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -423,6 +423,8 @@ 8565A34D1FC8DFE400239327 /* LaunchTabNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */; }; 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8577A1C4255D2C0D00D43FCD /* HitTestingToolbar.swift */; }; 857EEB752095FFAC008A005C /* HomeRowInstructionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 857EEB742095FFAC008A005C /* HomeRowInstructionsViewController.swift */; }; + 858479C92B8792D800D156C1 /* HistoryManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858479C82B8792D800D156C1 /* HistoryManager.swift */; }; + 858479CD2B87964500D156C1 /* HistoryManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858479CB2B8795C900D156C1 /* HistoryManagerTests.swift */; }; 858566E8252E4F56007501B8 /* Debug.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 858566E7252E4F56007501B8 /* Debug.storyboard */; }; 858566FB252E55D6007501B8 /* ImageCacheDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 858566FA252E55D6007501B8 /* ImageCacheDebugViewController.swift */; }; 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85864FBB24D31EF300E756FF /* SuggestionTrayViewController.swift */; }; @@ -1516,6 +1518,8 @@ 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotificationTests.swift; sourceTree = ""; }; 8577A1C4255D2C0D00D43FCD /* HitTestingToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HitTestingToolbar.swift; sourceTree = ""; }; 857EEB742095FFAC008A005C /* HomeRowInstructionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeRowInstructionsViewController.swift; sourceTree = ""; }; + 858479C82B8792D800D156C1 /* HistoryManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryManager.swift; sourceTree = ""; }; + 858479CB2B8795C900D156C1 /* HistoryManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryManagerTests.swift; sourceTree = ""; }; 858566E7252E4F56007501B8 /* Debug.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Debug.storyboard; sourceTree = ""; }; 858566FA252E55D6007501B8 /* ImageCacheDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCacheDebugViewController.swift; sourceTree = ""; }; 85864FBB24D31EF300E756FF /* SuggestionTrayViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuggestionTrayViewController.swift; sourceTree = ""; }; @@ -3874,6 +3878,22 @@ path = OpenAction; sourceTree = ""; }; + 858479C72B8792C900D156C1 /* History */ = { + isa = PBXGroup; + children = ( + 858479C82B8792D800D156C1 /* HistoryManager.swift */, + ); + name = History; + sourceTree = ""; + }; + 858479CA2B8795BF00D156C1 /* History */ = { + isa = PBXGroup; + children = ( + 858479CB2B8795C900D156C1 /* HistoryManagerTests.swift */, + ); + name = History; + sourceTree = ""; + }; 858566F1252E55AE007501B8 /* Debug */ = { isa = PBXGroup; children = ( @@ -5015,6 +5035,7 @@ F143C2E51E4A4CD400CFDE3A /* Core */ = { isa = PBXGroup; children = ( + 858479C72B8792C900D156C1 /* History */, EE7A92852AC6DE2500832A36 /* NetworkProtection */, 4B470ED4299C484B0086EBDC /* AppTrackingProtection */, F1CE42A71ECA0A520074A8DF /* Bookmarks */, @@ -5457,6 +5478,7 @@ F1E092B31E92A6B900732CCC /* Core */ = { isa = PBXGroup; children = ( + 858479CA2B8795BF00D156C1 /* History */, 4B83396D29AC0F22003F7EA9 /* AppTrackingProtection */, EA7EFE662677F5BD0075464E /* PrivacyReferenceTests */, 83EDCC3E1F86B363005CDFCD /* API */, @@ -6993,6 +7015,7 @@ F14E491F1E391CE900DC037C /* URLExtensionTests.swift in Sources */, 85D2187424BF25CD004373D2 /* FaviconsTests.swift in Sources */, 85AD49EE2B6149110085D2D1 /* CookieStorageTests.swift in Sources */, + 858479CD2B87964500D156C1 /* HistoryManagerTests.swift in Sources */, CBCCF96828885DEE006F4A71 /* AppPrivacyConfigurationTests.swift in Sources */, 310742AB2848E6FD0012660B /* BackForwardMenuHistoryItemURLSanitizerTests.swift in Sources */, 22CB1ED8203DDD2C00D2C724 /* AppDeepLinksTests.swift in Sources */, @@ -7105,6 +7128,7 @@ buildActionMask = 2147483647; files = ( 4B470ED6299C49800086EBDC /* AppTrackingProtectionDatabase.swift in Sources */, + 858479C92B8792D800D156C1 /* HistoryManager.swift in Sources */, 0253A43129E5DCD7003697C1 /* AppTrackingProtectionAllowlistModel.swift in Sources */, F16393FF1ECCB9CC00DDD653 /* FileLoader.swift in Sources */, F1134EAB1F3E2C6A00B73467 /* StatisticsUserDefaults.swift in Sources */, @@ -9912,7 +9936,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 110.0.1; + version = 111.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index fc790ff80e..c76a792238 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "483427db845410f10121cf2200f5a940c9bbf70b", - "version" : "110.0.1" + "revision" : "418b186e3e015d56f73435e7f6c035e10e106aa6", + "version" : "111.0.0" } }, { diff --git a/DuckDuckGoTests/HistoryManagerTests.swift b/DuckDuckGoTests/HistoryManagerTests.swift new file mode 100644 index 0000000000..c969773b29 --- /dev/null +++ b/DuckDuckGoTests/HistoryManagerTests.swift @@ -0,0 +1,66 @@ +// +// HistoryManagerTests.swift +// DuckDuckGo +// +// 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 XCTest +import BrowserServicesKit +@testable import Core + +final class HistoryManagerTests: XCTestCase { + + let privacyConfig = MockPrivacyConfiguration() + var variantManager = MockVariantManager() + + lazy var historyManager: HistoryManager = { + HistoryManager(privacyConfig: privacyConfig, variantManager: variantManager) + }() + + func test() { + + struct Condition { + + let variant: Bool + let privacy: Bool + let expected: Bool + + } + + let conditions = [ + Condition(variant: true, privacy: true, expected: true), + Condition(variant: false, privacy: true, expected: false), + Condition(variant: true, privacy: false, expected: false), + ] + + let privacyConfig = MockPrivacyConfiguration() + var variantManager = MockVariantManager() + + for condition in conditions { + privacyConfig.isFeatureKeyEnabled = { feature, _ in + XCTAssertEqual(feature, .history) + return condition.privacy + } + + variantManager.isSupportedReturns = condition.variant + + let historyManager = HistoryManager(privacyConfig: privacyConfig, variantManager: variantManager) + XCTAssertEqual(condition.expected, historyManager.isHistoryFeatureEnabled(), String(describing: condition)) + } + + } +} diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 3833fc5480..3ee69e9686 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 702ea615d2..a293eb62d8 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 42616e968a..e62f89cfea 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "110.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.0"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From d17c8fa5e5124e8cdd6136247a0ee0009de7af6c Mon Sep 17 00:00:00 2001 From: Thom Espach Date: Mon, 26 Feb 2024 12:23:28 +0000 Subject: [PATCH 054/245] Fix sync crypto error handling to protect against DoS (#2485) Task/Issue URL: https://app.asana.com/0/0/1206634174761721/f Description: During a security audit, it was discovered that a lack of error handling in the ddgSyncCrypto library can cause unexpected crashes leading to remote DoS on iOS and macOS. This is a rare occurrence and requires some work to test, but the crux is: an unexpected exception due to invalid cipher text lengths returned by the Sync API. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fd8ea42b50..14dc93f628 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9936,7 +9936,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 111.0.0; + version = 111.0.2; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c76a792238..1eeff22c57 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "418b186e3e015d56f73435e7f6c035e10e106aa6", - "version" : "111.0.0" + "revision" : "04c35220aa94bd005171086acccadd677400e7d5", + "version" : "111.0.2" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 3ee69e9686..0ac99830a6 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index a293eb62d8..4769fb0741 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index e62f89cfea..0e1585dd42 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 4a55da6d370a89ddd6720680a39f99fdbe64d267 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 26 Feb 2024 15:04:14 +0000 Subject: [PATCH 055/245] Fixes the firing of `mh` pixel (#2503) Task/Issue URL: https://app.asana.com/0/414709148257752/1206674614649925/f Tech Design URL: CC: Description: Fixes the firing of mh pixel Steps to test this PR: Launch the app, ensure mh pixel is fired when the the new tab is shown Create tabs from the tab switcher. mh should only be fired when the the user ends up on the Home Screen Check RMF message still displays as expected --- DuckDuckGo/HomeViewController.swift | 10 ++++++---- DuckDuckGo/MainViewController.swift | 6 +++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/HomeViewController.swift b/DuckDuckGo/HomeViewController.swift index 3d79ff3747..e79caf0368 100644 --- a/DuckDuckGo/HomeViewController.swift +++ b/DuckDuckGo/HomeViewController.swift @@ -264,12 +264,14 @@ class HomeViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - - if presentedViewController == nil { // prevents these being called when settings forces this controller to be reattached - showNextDaxDialog() - } + + // If there's no tab switcher then this will be true, if there is a tabswitcher then only allow the + // stuff below to happen if it's being dismissed + guard presentedViewController?.isBeingDismissed ?? true else { return } Pixel.fire(pixel: .homeScreenShown) + showNextDaxDialog() + collectionView.didAppear() viewHasAppeared = true diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index c4b19b187d..0860b07266 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -2058,8 +2058,12 @@ extension MainViewController: TabSwitcherDelegate { func tabSwitcher(_ tabSwitcher: TabSwitcherViewController, didRemoveTab tab: Tab) { if tabManager.count == 1 { // Make sure UI updates finish before dimissing the view. + // However, as a result, viewDidAppear on the home controller thinks the tab + // switcher is still presented. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { - tabSwitcher.dismiss() + tabSwitcher.dismiss(animated: true) { + self.homeController?.viewDidAppear(true) + } } } closeTab(tab) From d3597eb447abf5d6f4802a1a55e66e5b33867fb5 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Mon, 26 Feb 2024 21:51:17 -0500 Subject: [PATCH 056/245] Release 7.110.0-0 (#2504) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 90 ++++++++++++++++--- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++------ DuckDuckGo/Settings.bundle/Root.plist | 2 +- fastlane/metadata/default/release_notes.txt | 1 - 6 files changed, 112 insertions(+), 43 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 26ca639588..6921fbe607 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.109.0 +MARKETING_VERSION = 7.110.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index f97aecbb98..2eb52672b6 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"c3bb1ebaa170bfc3543945de56cbc25e\"" - public static let embeddedDataSHA = "576368fbd8b9edd69b352fdf05250beb1f942d2eb00df3ff090b14a399ee5df0" + public static let embeddedDataETag = "\"20ceae04600e9d3c46898ac4471fcd70\"" + public static let embeddedDataSHA = "987f63a393724a34fe9d190c335f14b58398d2e9e02ccf38b84a7bd57c37b8ab" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index b13bcf949e..eaba26e1a1 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1707768517000, + "version": 1708953918019, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -257,9 +257,6 @@ { "domain": "youtube.com" }, - { - "domain": "tuc.org.uk" - }, { "domain": "newsmax.com" }, @@ -279,7 +276,7 @@ "settings": { "disabledCMPs": [ "generic-cosmetic", - "EZoic" + "termsfeed3" ] }, "state": "enabled", @@ -290,12 +287,18 @@ "steps": [ { "percent": 25 + }, + { + "percent": 50 + }, + { + "percent": 100 } ] } } }, - "hash": "d01ba9e47542c11b7dd4bf9d5f25d16b" + "hash": "7b3ce9784ee7348bb7a07d1868c0f3ae" }, "autofill": { "exceptions": [ @@ -1113,6 +1116,10 @@ "domain": "chase.com", "reason": "Login issues" }, + { + "domain": "www.canva.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1818" + }, { "domain": "duckduckgo.com", "reason": "Internal exclusion to roll out experiment" @@ -1124,13 +1131,16 @@ "omitApplicationSites": [ { "domain": "chase.com" + }, + { + "domain": "www.canva.com" } ], "omitVersionSites": [] }, "exceptions": [], "state": "enabled", - "hash": "9461d22c4f0a25b30a46cc53c0718a32" + "hash": "a0c51971630002ca1fe1b7825fba560d" }, "dbp": { "state": "disabled", @@ -4466,6 +4476,11 @@ "state": "disabled", "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" }, + "history": { + "state": "enabled", + "exceptions": [], + "hash": "697382e31649d84b01166f1dc6f790d6" + }, "https": { "state": "enabled", "exceptions": [ @@ -4828,6 +4843,16 @@ } ] }, + "adlightning.com": { + "rules": [ + { + "rule": "publisher.adlightning.com/user-api/session/", + "domains": [ + "boltive.com" + ] + } + ] + }, "ads-twitter.com": { "rules": [ { @@ -5174,7 +5199,19 @@ "cloudflare.com": { "rules": [ { - "rule": "cdnjs.cloudflare.com/cdn-cgi/scripts/.*/cloudflare-static/rocket-loader.min.js", + "rule": "cloudflare.com/cdn-cgi/scripts/7089c43e/cloudflare-static/rocket-loader.min.js", + "domains": [ + "" + ] + }, + { + "rule": "cloudflare.com/cdn-cgi/scripts/7d0fa10a/cloudflare-static/rocket-loader.min.js", + "domains": [ + "" + ] + }, + { + "rule": "cloudflare.com/cdn-cgi/scripts/1680307200/cloudflare-static/rocket-loader.min.js", "domains": [ "" ] @@ -5859,6 +5896,7 @@ "doterra.com", "easyjet.com", "edx.org", + "saplinglearning.com", "worlddutyfree.com" ] }, @@ -6031,7 +6069,9 @@ "rule": "googletagmanager.com/gtag/js", "domains": [ "abril.com.br", + "algomalegalclinic.com", "cosmicbook.news", + "eatroyo.com", "thesimsresource.com", "tradersync.com" ] @@ -6983,6 +7023,16 @@ } ] }, + "pubnation.com": { + "rules": [ + { + "rule": "scripts.pubnation.com/tags/", + "domains": [ + "n4g.com" + ] + } + ] + }, "qualtrics.com": { "rules": [ { @@ -7242,6 +7292,16 @@ } ] }, + "sumo.com": { + "rules": [ + { + "rule": "load.sumo.com", + "domains": [ + "glennbeck.com" + ] + } + ] + }, "taboola.com": { "rules": [ { @@ -7668,7 +7728,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "174d90955e35a7140bcbe16d8764b52c" + "hash": "86a7c891d57513e67356a82da2a2aa1d" }, "trackingCookies1p": { "settings": { @@ -7836,10 +7896,20 @@ "value": "enabled" } ] + }, + { + "domain": "myhome.experian.co.uk", + "patchSettings": [ + { + "op": "replace", + "path": "/messageHandlers/state", + "value": "enabled" + } + ] } ] }, - "hash": "4e94cff79e689ff320de22a57e242bdd" + "hash": "592a1fb6314f04875fc44a66ef7c2433" }, "windowsPermissionUsage": { "exceptions": [], diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 14dc93f628..1479662e48 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8161,7 +8161,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8198,7 +8198,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8290,7 +8290,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8318,7 +8318,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8468,7 +8468,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8494,7 +8494,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8559,7 +8559,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8594,7 +8594,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8628,7 +8628,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8659,7 +8659,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8946,7 +8946,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8977,7 +8977,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9006,7 +9006,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9040,7 +9040,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9071,7 +9071,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9104,11 +9104,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9346,7 +9346,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9373,7 +9373,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9406,7 +9406,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9444,7 +9444,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9480,7 +9480,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9515,11 +9515,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9693,11 +9693,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9726,10 +9726,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 7999c82620..a984845555 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.109.0 + 7.110.0 Key version Title diff --git a/fastlane/metadata/default/release_notes.txt b/fastlane/metadata/default/release_notes.txt index 3e40ce3707..6cd7a9e939 100644 --- a/fastlane/metadata/default/release_notes.txt +++ b/fastlane/metadata/default/release_notes.txt @@ -1,4 +1,3 @@ -- We fixed a bug preventing the widgets working on older versions of iOS - Bug fixes and other improvements. Join our fully distributed team and help raise the standard of trust online! https://duckduckgo.com/hiring From eb0e479e363bef6efdd8376599e0ba0bc310bfb5 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 27 Feb 2024 17:08:50 +0100 Subject: [PATCH 057/245] 13. Iterate on Subscription Changes (#2498) Task/Issue URL: https://app.asana.com/0/0/1206362842825232/f Description: This includes several copy and small UI changes for the iOS Subscription flow based on user feedback. Memory management updates to prevent a leak on the subscriber feature and thoroughly deallocating the webviews --- DuckDuckGo/SettingsSubscriptionView.swift | 14 ++ DuckDuckGo/SettingsViewModel.swift | 4 + .../AsyncHeadlessWebViewModel.swift | 2 +- .../HeadlessWebView.swift | 31 +++- .../HeadlessWebViewCoordinator.swift | 61 ++++++- .../Privacy-Pro-96x96.imageset/Contents.json | 12 ++ .../Privacy-Pro-96x96.svg | 13 ++ ...IdentityTheftRestorationPagesFeature.swift | 6 +- ...scriptionPagesUseSubscriptionFeature.swift | 25 ++- .../SubscriptionEmailViewModel.swift | 9 +- .../SubscriptionExternalLinkViewModel.swift | 10 +- .../ViewModel/SubscriptionFlowViewModel.swift | 42 +++-- .../ViewModel/SubscriptionITPViewModel.swift | 21 +-- .../SubscriptionRestoreViewModel.swift | 5 +- .../SubscriptionSettingsViewModel.swift | 50 +++++- .../Views/SubscriptionFlowView.swift | 27 ++- .../Views/SubscriptionRestoreView.swift | 93 ++++++---- .../Views/SubscriptionSettingsView.swift | 163 ++++++++++++------ DuckDuckGo/UserText.swift | 38 ++-- DuckDuckGo/en.lproj/Localizable.strings | 56 ++++-- submodules/privacy-reference-tests | 2 +- 21 files changed, 501 insertions(+), 183 deletions(-) create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Privacy-Pro-96x96.svg diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index d9e11e2acd..c409974387 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -47,6 +47,12 @@ struct SettingsSubscriptionView: View { .foregroundColor(Color.init(designSystemColor: .accent)) } + private var iHaveASubscriptionView: some View { + Text(UserText.settingsPProIHaveASubscription) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent)) + } + private var manageSubscriptionView: some View { Text(UserText.settingsPProManageSubscription) .daxBodyRegular() @@ -62,6 +68,14 @@ struct SettingsSubscriptionView: View { .sheet(isPresented: $isShowingsubScriptionFlow) { SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() } + + SettingsCustomCell(content: { iHaveASubscriptionView }, + action: { + isShowingsubScriptionFlow = true + subscriptionFlowViewModel.activateSubscriptionOnLoad = true + }, + isButton: true ) + } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 2064972949..1623d2e0c8 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -342,10 +342,14 @@ extension SettingsViewModel { case .success(let response) where !response.isSubscriptionActive: AccountManager().signOut() setupSubscriptionPurchaseOptions() + case .success(let response): + // Cache Subscription state + self.state.subscription.hasActiveSubscription = true Self.cachedHasActiveSubscription = self.state.subscription.hasActiveSubscription + // Check entitlements and update UI accordingly let entitlements: [AccountManager.Entitlement] = [.identityTheftRestoration, .dataBrokerProtection, .networkProtection] for entitlement in entitlements { diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift index ef5b1878fe..190352d246 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift @@ -22,7 +22,7 @@ import Core import Combine final class AsyncHeadlessWebViewViewModel: ObservableObject { - let userScript: UserScriptMessaging? + weak var userScript: UserScriptMessaging? let subFeature: Subfeature? let settings: AsyncHeadlessWebViewSettings diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift index a0e2acd8c6..7467489ac2 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift @@ -24,7 +24,7 @@ import UserScript import BrowserServicesKit struct HeadlessWebView: UIViewRepresentable { - let userScript: UserScriptMessaging? + weak var userScript: UserScriptMessaging? let subFeature: Subfeature? let settings: AsyncHeadlessWebViewSettings var onScroll: ((CGPoint) -> Void)? @@ -66,7 +66,8 @@ struct HeadlessWebView: UIViewRepresentable { func updateUIView(_ uiView: WKWebView, context: Context) {} func makeCoordinator() -> HeadlessWebViewCoordinator { - HeadlessWebViewCoordinator(self, + let currentViewController = UIViewController.getCurrentViewController() + return HeadlessWebViewCoordinator(self, presenter: currentViewController, onScroll: onScroll, onURLChange: onURLChange, onCanGoBack: onCanGoBack, @@ -98,5 +99,31 @@ struct HeadlessWebView: UIViewRepresentable { } return userContentController } + + static func dismantleUIView(_ uiView: WKWebView, coordinator: HeadlessWebViewCoordinator) { + uiView.stopLoading() + uiView.uiDelegate = nil + uiView.navigationDelegate = nil + uiView.scrollView.delegate = nil + uiView.configuration.userContentController = WKUserContentController() + uiView.configuration.userContentController.removeAllScriptMessageHandlers() + uiView.configuration.userContentController.removeAllContentRuleLists() + uiView.configuration.userContentController.removeAllUserScripts() + coordinator.cleanUp() + + } } + +extension UIViewController { + static func getCurrentViewController(base: UIViewController? = UIApplication.shared.windows.first { $0.isKeyWindow }?.rootViewController) -> UIViewController? { + if let nav = base as? UINavigationController { + return getCurrentViewController(base: nav.visibleViewController) + } else if let tab = base as? UITabBarController, let selected = tab.selectedViewController { + return getCurrentViewController(base: selected) + } else if let presented = base?.presentedViewController { + return getCurrentViewController(base: presented) + } + return base + } +} diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift index dd14a819ee..92c8f1a7a4 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift @@ -21,7 +21,10 @@ import Foundation import WebKit final class HeadlessWebViewCoordinator: NSObject { + var parent: HeadlessWebView + weak var presenter: UIViewController? + var onScroll: ((CGPoint) -> Void)? var onURLChange: ((URL) -> Void)? var onCanGoBack: ((Bool) -> Void)? @@ -43,6 +46,7 @@ final class HeadlessWebViewCoordinator: NSObject { private var webViewCanGoForwardObservation: NSKeyValueObservation? init(_ parent: HeadlessWebView, + presenter: UIViewController?, onScroll: ((CGPoint) -> Void)?, onURLChange: ((URL) -> Void)?, onCanGoBack: ((Bool) -> Void)?, @@ -51,6 +55,7 @@ final class HeadlessWebViewCoordinator: NSObject { allowedDomains: [String]? = nil, settings: AsyncHeadlessWebViewSettings = AsyncHeadlessWebViewSettings()) { self.parent = parent + self.presenter = presenter self.onScroll = onScroll self.onURLChange = onURLChange self.onCanGoBack = onCanGoBack @@ -62,23 +67,46 @@ final class HeadlessWebViewCoordinator: NSObject { func setupWebViewObservation(_ webView: WKWebView) { webViewURLObservation = webView.observe(\.url, options: [.new]) { [weak self] _, change in if let newURL = change.newValue as? URL { - self?.onURLChange?(newURL) - self?.onCanGoBack?(webView.canGoBack) + DispatchQueue.main.async { + self?.onURLChange?(newURL) + self?.onCanGoBack?(webView.canGoBack) + } } } webViewCanGoBackObservation = webView.observe(\.canGoBack, options: [.new]) { [weak self] _, change in if let canGoBack = change.newValue { - self?.onCanGoBack?(canGoBack) + DispatchQueue.main.async { + self?.onCanGoBack?(canGoBack) + } } } webViewCanGoForwardObservation = webView.observe(\.canGoForward, options: [.new]) { [weak self] _, change in if let onCanGoForward = change.newValue { - self?.onCanGoForward?(onCanGoForward) + DispatchQueue.main.async { + self?.onCanGoForward?(onCanGoForward) + } } } } + + // Called from the webView dismantle + func cleanUp() { + webViewURLObservation?.invalidate() + webViewCanGoBackObservation?.invalidate() + webViewCanGoForwardObservation?.invalidate() + + webViewURLObservation = nil + webViewCanGoBackObservation = nil + webViewCanGoForwardObservation = nil + + onScroll = nil + onURLChange = nil + onCanGoBack = nil + onCanGoForward = nil + onContentType = nil + } } @@ -158,4 +186,29 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { // NOOP } + // Javascript Confirm dialogs Delegate + func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) { + guard let presenter = presenter else { + completionHandler(false) + return + } + + let alertController = UIAlertController(title: UserText.subscriptionConfirmTitle, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: UserText.actionOK, style: .default, handler: { _ in completionHandler(true) })) + alertController.addAction(UIAlertAction(title: UserText.actionCancel, style: .cancel, handler: { _ in completionHandler(false) })) + presenter.present(alertController, animated: true, completion: nil) + } + + // Javascript Confirm alert dialogs + func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) { + guard let presenter = presenter else { + completionHandler() + return + } + + let alertController = UIAlertController(title: UserText.subscriptionAlertTitle, message: message, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: UserText.actionOK, style: .default, handler: { _ in completionHandler() })) + presenter.present(alertController, animated: true, completion: nil) + } + } diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Contents.json new file mode 100644 index 0000000000..d407c89ceb --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Privacy-Pro-96x96.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Privacy-Pro-96x96.svg b/DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Privacy-Pro-96x96.svg new file mode 100644 index 0000000000..58a03b528e --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/Privacy-Pro-96x96.imageset/Privacy-Pro-96x96.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index b094c71124..055e3bc1cf 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -44,7 +44,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { } - var broker: UserScriptMessageBroker? + weak var broker: UserScriptMessageBroker? var featureName: String = Constants.featureName var messageOriginPolicy: MessageOriginPolicy = .only(rules: [ @@ -70,6 +70,10 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { let authToken = AccountManager().authToken ?? "" return Subscription(token: authToken) } + + deinit { + broker = nil + } } #endif diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index dbfef2b806..e7013f52cd 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -26,6 +26,10 @@ import UserScript import Combine import Subscription +enum SubscriptionTransactionStatus { + case idle, purchasing, restoring, polling +} + @available(iOS 15.0, *) final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObject { @@ -59,23 +63,19 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec static let month = "monthly" static let year = "yearly" } - - enum TransactionStatus { - case idle, purchasing, restoring, polling - } - + struct FeatureSelection: Codable { let feature: String } - @Published var transactionStatus: TransactionStatus = .idle + @Published var transactionStatus: SubscriptionTransactionStatus = .idle @Published var hasActiveSubscription = false @Published var purchaseError: AppStorePurchaseFlow.Error? @Published var activateSubscription: Bool = false @Published var emailActivationComplete: Bool = false @Published var selectedFeature: FeatureSelection? - var broker: UserScriptMessageBroker? + weak var broker: UserScriptMessageBroker? var featureName = Constants.featureName @@ -274,5 +274,16 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } } + + func cleanup() { + transactionStatus = .idle + hasActiveSubscription = false + purchaseError = nil + activateSubscription = false + emailActivationComplete = false + selectedFeature = nil + broker = nil + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 2bcb373c9e..d519251bc7 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -32,12 +32,12 @@ final class SubscriptionEmailViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature var emailURL = URL.activateSubscriptionViaEmail - var viewTitle = UserText.subscriptionRestoreEmail + var viewTitle = UserText.subscriptionActivateEmail @Published var subscriptionEmail: String? @Published var shouldReloadWebView = false @Published var activateSubscription = false @Published var managingSubscriptionEmail = false - @Published var webViewModel: AsyncHeadlessWebViewViewModel + var webViewModel: AsyncHeadlessWebViewViewModel private static let allowedDomains = [ "duckduckgo.com", @@ -92,6 +92,11 @@ final class SubscriptionEmailViewModel: ObservableObject { func loadURL() { webViewModel.navigationCoordinator.navigateTo(url: emailURL ) } + + deinit { + cancellables.removeAll() + + } } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift index 391b430874..fd1c2557a7 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift @@ -27,13 +27,13 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { var url: URL var allowedDomains: [String]? + var webViewModel: AsyncHeadlessWebViewViewModel + private var canGoBackCancellable: AnyCancellable? + private var cancellables = Set() - @Published var webViewModel: AsyncHeadlessWebViewViewModel @Published var canNavigateBack: Bool = false - private var cancellables = Set() - init(url: URL, allowedDomains: [String]? = nil) { let webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: allowedDomains, @@ -64,5 +64,9 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { await webViewModel.navigationCoordinator.goBack() } + deinit { + cancellables.removeAll() + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index f8b1cc0220..4afbdae45b 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -32,9 +32,10 @@ final class SubscriptionFlowViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature let purchaseManager: PurchaseManager let viewTitle = UserText.settingsPProSection + var webViewModel: AsyncHeadlessWebViewViewModel enum Constants { - static let navigationBarHideThreshold = 40.0 + static let navigationBarHideThreshold = 80.0 } private var cancellables = Set() @@ -51,14 +52,14 @@ final class SubscriptionFlowViewModel: ObservableObject { // Published properties @Published var hasActiveSubscription = false - @Published var transactionStatus: SubscriptionPagesUseSubscriptionFeature.TransactionStatus = .idle - @Published var activatingSubscription = false + @Published var transactionStatus: SubscriptionTransactionStatus = .idle + @Published var userTappedRestoreButton = false + @Published var activateSubscriptionOnLoad: Bool = false @Published var shouldDismissView = false - @Published var webViewModel: AsyncHeadlessWebViewViewModel @Published var shouldShowNavigationBar: Bool = false @Published var selectedFeature: SettingsViewModel.SettingsSection? @Published var canNavigateBack: Bool = false - + private static let allowedDomains = [ "duckduckgo.com", "microsoftonline.com", @@ -86,10 +87,12 @@ final class SubscriptionFlowViewModel: ObservableObject { private func setupTransactionObserver() async { subFeature.$transactionStatus + .receive(on: DispatchQueue.main) .sink { [weak self] status in - guard let self = self else { return } - Task { await self.setTransactionStatus(status) } - + guard let strongSelf = self else { return } + Task { + await strongSelf.setTransactionStatus(status) + } } .store(in: &cancellables) @@ -104,8 +107,7 @@ final class SubscriptionFlowViewModel: ObservableObject { .receive(on: DispatchQueue.main) .sink { [weak self] value in if value { - self?.subFeature.activateSubscription = false - self?.activatingSubscription = true + self?.userTappedRestoreButton = true } } .store(in: &cancellables) @@ -129,11 +131,16 @@ final class SubscriptionFlowViewModel: ObservableObject { } .store(in: &cancellables) - + + } + + private func setupWebViewObservers() async { webViewModel.$scrollPosition .receive(on: DispatchQueue.main) .sink { [weak self] value in - self?.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold + DispatchQueue.main.async { + self?.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold + } } .store(in: &cancellables) @@ -145,7 +152,7 @@ final class SubscriptionFlowViewModel: ObservableObject { } @MainActor - private func setTransactionStatus(_ status: SubscriptionPagesUseSubscriptionFeature.TransactionStatus) { + private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { self.transactionStatus = status } @@ -157,21 +164,26 @@ final class SubscriptionFlowViewModel: ObservableObject { func initializeViewData() async { await self.setupTransactionObserver() + await self .setupWebViewObservers() await self.updateSubscriptionStatus() webViewModel.navigationCoordinator.navigateTo(url: purchaseURL ) } func finalizeSubscriptionFlow() { canGoBackCancellable?.cancel() - cancellables.removeAll() subFeature.selectedFeature = nil hasActiveSubscription = false transactionStatus = .idle - activatingSubscription = false + userTappedRestoreButton = false shouldShowNavigationBar = false selectedFeature = nil canNavigateBack = false shouldDismissView = true + subFeature.cleanup() + } + + deinit { + cancellables.removeAll() } func restoreAppstoreTransaction() { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 0ff8125eb8..04432065ec 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -27,13 +27,13 @@ import Subscription @available(iOS 15.0, *) final class SubscriptionITPViewModel: ObservableObject { - let userScript: IdentityTheftRestorationPagesUserScript - let subFeature: IdentityTheftRestorationPagesFeature + var userScript: IdentityTheftRestorationPagesUserScript? + var subFeature: IdentityTheftRestorationPagesFeature? var manageITPURL = URL.identityTheftRestoration var viewTitle = UserText.subscriptionTitle enum Constants { - static let navigationBarHideThreshold = 60.0 + static let navigationBarHideThreshold = 15.0 static let downloadableContent = ["application/pdf"] static let blankURL = "about:blank" static let externalSchemes = ["tel", "sms", "facetime"] @@ -41,12 +41,12 @@ final class SubscriptionITPViewModel: ObservableObject { // State variables var itpURL = URL.identityTheftRestoration - @Published var webViewModel: AsyncHeadlessWebViewViewModel @Published var shouldShowNavigationBar: Bool = false @Published var canNavigateBack: Bool = false @Published var isDownloadableContent: Bool = false @Published var activityItems: [Any] = [] @Published var attachmentURL: URL? + var webViewModel: AsyncHeadlessWebViewViewModel @Published var shouldNavigateToExternalURL: URL? var shouldShowExternalURLSheet: Bool { @@ -101,12 +101,7 @@ final class SubscriptionITPViewModel: ObservableObject { strongSelf.isDownloadableContent = true guard let url = strongSelf.currentURL else { return } Task { - // We are using a dummy PDF for testing, as the real PDF's are behind the internal user login - if let downloadURL = URL(string: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf") { - await strongSelf.downloadAttachment(from: downloadURL) - } - // if let downloadURL = url { - // await strongSelf.downloadAttachment(from: downloadURL) + await strongSelf.downloadAttachment(from: url) } } } @@ -186,5 +181,11 @@ final class SubscriptionITPViewModel: ObservableObject { await webViewModel.navigationCoordinator.goBack() } + deinit { + cancellables.removeAll() + self.userScript = nil + self.subFeature = nil + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 666c6fe77e..4fca7a32d8 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -37,7 +37,7 @@ final class SubscriptionRestoreViewModel: ObservableObject { case unknown, activated, notFound, error } - @Published var transactionStatus: SubscriptionPagesUseSubscriptionFeature.TransactionStatus = .idle + @Published var transactionStatus: SubscriptionTransactionStatus = .idle @Published var activationResult: SubscriptionActivationResult = .unknown @Published var subscriptionEmail: String? @@ -62,7 +62,7 @@ final class SubscriptionRestoreViewModel: ObservableObject { } @MainActor - private func setTransactionStatus(_ status: SubscriptionPagesUseSubscriptionFeature.TransactionStatus) { + private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { self.transactionStatus = status } @@ -80,5 +80,6 @@ final class SubscriptionRestoreViewModel: ObservableObject { } } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 3619b69b95..d798eac708 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -26,40 +26,70 @@ import Subscription @available(iOS 15.0, *) final class SubscriptionSettingsViewModel: ObservableObject { + enum Constants { + static let autoRenewable = "Auto-Renewable" + static let notAutoRenewable = "Not Auto-Renewable" + static let monthlyProductID = "ios.subscription.1month" + static let yearlyProductID = "ios.subscription.1year" + static let updateFrequency: Float = 10 + } + let accountManager: AccountManager - var subscriptionDetails: String = "" + private var subscriptionUpdateTimer: Timer? + @Published var subscriptionDetails: String = "" + @Published var subscriptionType: String = "" @Published var shouldDisplayRemovalNotice: Bool = false + @Published var shouldDismissView: Bool = false init(accountManager: AccountManager = AccountManager()) { self.accountManager = accountManager - fetchAndUpdateSubscriptionDetails() + Task { await fetchAndUpdateSubscriptionDetails() } + setupSubscriptionUpdater() } private var dateFormatter: DateFormatter = { let formatter = DateFormatter() - formatter.dateFormat = "MMM dd, yyyy" // Jan 12, 2024" + formatter.dateFormat = "MMM dd, yyyy" return formatter }() - private func fetchAndUpdateSubscriptionDetails() { + @MainActor + func fetchAndUpdateSubscriptionDetails() { Task { guard let token = accountManager.accessToken else { return } - if let cachedDate = SubscriptionService.cachedSubscriptionDetailsResponse?.expiresOrRenewsAt { - updateSubscriptionDetails(date: cachedDate) + if let cachedDate = SubscriptionService.cachedSubscriptionDetailsResponse?.expiresOrRenewsAt, + let cachedStatus = SubscriptionService.cachedSubscriptionDetailsResponse?.status, + let productID = SubscriptionService.cachedSubscriptionDetailsResponse?.productId { + updateSubscriptionDetails(status: cachedStatus, date: cachedDate, product: productID) } if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { if !response.isSubscriptionActive { AccountManager().signOut() + shouldDismissView = true return + } else { + updateSubscriptionDetails(status: response.status, date: response.expiresOrRenewsAt, product: response.productId) } } } } - private func updateSubscriptionDetails(date: Date) { - self.subscriptionDetails = UserText.subscriptionInfo(expiration: dateFormatter.string(from: date)) + private func setupSubscriptionUpdater() { + subscriptionUpdateTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { [weak self] _ in + guard let strongSelf = self else { return } + Task { + await strongSelf.fetchAndUpdateSubscriptionDetails() + } + } + } + + + private func updateSubscriptionDetails(status: String, date: Date, product: String) { + let statusString = (status == Self.Constants.autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires + self.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) + self.subscriptionType = product == Constants.monthlyProductID ? UserText.subscriptionMonthly : UserText.subscriptionAnnual } func removeSubscription() { @@ -88,6 +118,10 @@ final class SubscriptionSettingsViewModel: ObservableObject { } } + deinit { + subscriptionUpdateTimer?.invalidate() + } + } #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index d8a2064a8d..1063e43854 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -60,6 +60,7 @@ struct SubscriptionFlowView: View { .navigationBarTitleDisplayMode(.inline) .navigationBarHidden(!viewModel.shouldShowNavigationBar).animation(.easeOut) } + .applyInsetGroupedListStyle() .tint(Color(designSystemColor: .textPrimary)) .environment(\.rootPresentationMode, self.$isActive) } @@ -130,17 +131,27 @@ struct SubscriptionFlowView: View { } } - .onChange(of: viewModel.activatingSubscription) { value in - if value { + .onChange(of: viewModel.userTappedRestoreButton) { _ in isActive = true - viewModel.activatingSubscription = false - } + viewModel.userTappedRestoreButton = false } .onAppear(perform: { setUpAppearances() Task { await viewModel.initializeViewData() } + // Display the Restore page on load if required (With no animation) + if viewModel.activateSubscriptionOnLoad { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2) { + var transaction = Transaction() + transaction.disablesAnimations = true + withTransaction(transaction) { + isActive = true + viewModel.activateSubscriptionOnLoad = false + } + + } + } }) .alert(isPresented: $isAlertVisible) { @@ -164,10 +175,12 @@ struct SubscriptionFlowView: View { private var webView: some View { ZStack(alignment: .top) { - // Restore View Hidden Link + + // Restore View Hidden Link NavigationLink(destination: SubscriptionRestoreView(), isActive: $isActive) { EmptyView() }.isDetailLink(false) + AsyncHeadlessWebView(viewModel: viewModel.webViewModel) .background() @@ -181,8 +194,8 @@ struct SubscriptionFlowView: View { private func setUpAppearances() { let navAppearance = UINavigationBar.appearance() - navAppearance.backgroundColor = UIColor(designSystemColor: .surface) - navAppearance.barTintColor = UIColor(designSystemColor: .surface) + navAppearance.backgroundColor = UIColor(designSystemColor: .background) + navAppearance.barTintColor = UIColor(designSystemColor: .container) navAppearance.shadowImage = UIImage() navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 2301cf7daa..1d654701aa 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -42,7 +42,7 @@ struct SubscriptionRestoreView: View { static let buttonCornerRadius = 8.0 static let buttonInsets = EdgeInsets(top: 10.0, leading: 16.0, bottom: 10.0, trailing: 16.0) static let cellLineSpacing = 12.0 - static let cellPadding = 4.0 + static let cellPadding = 8.0 static let headerPadding = EdgeInsets(top: 16.0, leading: 16.0, bottom: 0, trailing: 16.0) } @@ -58,10 +58,10 @@ struct SubscriptionRestoreView: View { headerView listView } - .background(Color(designSystemColor: .container)) .navigationTitle(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceTitle : UserText.subscriptionActivate) .navigationBarBackButtonHidden(viewModel.transactionStatus != .idle) .applyInsetGroupedListStyle() + .alert(isPresented: $isAlertVisible) { getAlert() } .onChange(of: viewModel.activationResult) { result in @@ -71,6 +71,7 @@ struct SubscriptionRestoreView: View { } .onAppear { viewModel.initializeView() + setUpAppearances() } if viewModel.transactionStatus != .idle { @@ -83,10 +84,6 @@ struct SubscriptionRestoreView: View { private var listItems: [ListItem] { [ .init(id: 0, - content: getCellTitle(icon: Constants.appleIDIcon, - text: UserText.subscriptionActivateAppleID), - expandedContent: getAppleIDCellContent(buttonAction: viewModel.restoreAppstoreTransaction)), - .init(id: 1, content: getCellTitle(icon: Constants.emailIcon, text: UserText.subscriptionActivateEmail), expandedContent: getEmailCellContent(buttonAction: { isActive = true })) @@ -104,40 +101,32 @@ struct SubscriptionRestoreView: View { ) } - private func getAppleIDCellContent(buttonAction: @escaping () -> Void) -> AnyView { + private func getEmailCellContent(buttonAction: @escaping () -> Void) -> AnyView { AnyView( VStack(alignment: .leading) { - Text(viewModel.isAddingDevice ? UserText.subscriptionAvailableInApple : UserText.subscriptionActivateAppleIDDescription) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) if !viewModel.isAddingDevice { - getCellButton(buttonText: UserText.subscriptionActivateAppleIDButton, action: buttonAction) - } - } - ) - } - - private func getEmailCellContent(buttonAction: @escaping () -> Void) -> AnyView { - AnyView( - VStack(alignment: .leading) { - if viewModel.subscriptionEmail == nil { - Text(UserText.subscriptionActivateEmailDescription) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - getCellButton(buttonText: UserText.subscriptionRestoreEmail, - action: buttonAction) - } else { - Text(viewModel.subscriptionEmail ?? "").daxSubheadSemibold() - Text(UserText.subscriptionActivateEmailDescription) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - HStack { - getCellButton(buttonText: UserText.subscriptionManageEmailButton, - action: buttonAction) - } + Text(UserText.subscriptionActivateEmailDescription) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + getCellButton(buttonText: UserText.subscriptionActivateEmailButton, + action: buttonAction) + } else if viewModel.subscriptionEmail == nil { + Text(UserText.subscriptionAddDeviceEmailDescription) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + getCellButton(buttonText: UserText.subscriptionRestoreAddEmailButton, + action: buttonAction) + } else { + Text(viewModel.subscriptionEmail ?? "").daxSubheadSemibold() + Text(UserText.subscriptionManageEmailDescription) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + HStack { + getCellButton(buttonText: UserText.subscriptionManageEmailButton, + action: buttonAction) } } - ) + }) } private func getCellButton(buttonText: String, action: @escaping () -> Void) -> AnyView { @@ -185,16 +174,36 @@ struct SubscriptionRestoreView: View { }.padding(Constants.headerPadding) } + @ViewBuilder + private var footerView: some View { + if !viewModel.isAddingDevice { + VStack(alignment: .leading) { + Text(UserText.subscriptionActivateDescription) + .daxFootnoteRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + Button(action: { + viewModel.restoreAppstoreTransaction() + }, label: { + Text(UserText.subscriptionRestoreAppleID) + .daxFootnoteSemibold() + .foregroundColor(Color(designSystemColor: .accent)) + }) + }.padding(.top, Constants.headerLineSpacing) + } + } + private var listView: some View { List { - Section { + Section(footer: footerView) { ForEach(Array(zip(listItems.indices, listItems)), id: \.1.id) { _, item in VStack(alignment: .leading, spacing: Constants.cellLineSpacing) { HStack { item.content Spacer() - Image(systemName: expandedItemId == item.id ? Constants.openIndicator : Constants.closedIndicator) - .foregroundColor(Color(designSystemColor: .textPrimary)) + if listItems.count > 1 { + Image(systemName: expandedItemId == item.id ? Constants.openIndicator : Constants.closedIndicator) + .foregroundColor(Color(designSystemColor: .textPrimary)) + } } .contentShape(Rectangle()) .onTapGesture { @@ -233,6 +242,14 @@ struct SubscriptionRestoreView: View { } } + private func setUpAppearances() { + let navAppearance = UINavigationBar.appearance() + navAppearance.backgroundColor = UIColor(designSystemColor: .background) + navAppearance.barTintColor = UIColor(designSystemColor: .surface) + navAppearance.shadowImage = UIImage() + navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) + } + struct ListItem { let id: Int let content: AnyView diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index 49b2b76aa3..ee294c65d8 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -30,73 +30,122 @@ class SceneEnvironment: ObservableObject { struct SubscriptionSettingsView: View { @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @StateObject var viewModel = SubscriptionSettingsViewModel() @StateObject var sceneEnvironment = SceneEnvironment() - var body: some View { - List { - Section(header: Text(viewModel.subscriptionDetails) - .lineLimit(nil) - .daxBodyRegular() - .fixedSize(horizontal: false, vertical: true)) { - EmptyView() - .frame(height: 0) - .hidden() - }.textCase(nil) - Section(header: Text(UserText.subscriptionManageDevices)) { - - - NavigationLink(destination: SubscriptionRestoreView()) { - SettingsCustomCell(content: { - Text(UserText.subscriptionAddDeviceButton) - .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent)) - }) - } - - - SettingsCustomCell(content: { - Text(UserText.subscriptionRemoveFromDevice) - .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent))}, - action: { viewModel.shouldDisplayRemovalNotice.toggle() }, - isButton: true) - + @ViewBuilder + private var listView: some View { + List { + Section { + VStack(alignment: .center, spacing: 7) { + Image("Privacy-Pro-96x96") + Text(UserText.subscriptionTitle).daxTitle2() + Text(viewModel.subscriptionType).daxHeadline() + Text(viewModel.subscriptionDetails) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) } - Section(header: Text(UserText.subscriptionManagePlan)) { + } + .listRowBackground(Color.clear) + .frame(maxWidth: .infinity, alignment: .center) + + Section(header: Text(UserText.subscriptionManageTitle)) { + SettingsCustomCell(content: { + Text(UserText.subscriptionChangePlan) + .daxBodyRegular() + }, + action: { Task { viewModel.manageSubscription() } }, + isButton: true) + } + + Section(header: Text(UserText.subscriptionManageDevices)) { + + NavigationLink(destination: SubscriptionRestoreView()) { SettingsCustomCell(content: { - Text(UserText.subscriptionChangePlan) + Text(UserText.subscriptionAddDeviceButton) .daxBodyRegular() - }, - action: { Task { viewModel.manageSubscription() } }, - isButton: true) + .foregroundColor(Color.init(designSystemColor: .accent)) + }) } - Section(header: Text(UserText.subscriptionHelpAndSupport), - footer: Text(UserText.subscriptionFAQFooter)) { - NavigationLink(destination: Text(UserText.subscriptionFAQ)) { - SettingsCustomCell(content: { - Text(UserText.subscriptionFAQ) - .daxBodyRegular() - }) - } + + SettingsCustomCell(content: { + Text(UserText.subscriptionRemoveFromDevice) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent))}, + action: { viewModel.shouldDisplayRemovalNotice.toggle() }, + isButton: true) + + } + + Section(header: Text(UserText.subscriptionHelpAndSupport), + footer: Text(UserText.subscriptionFAQFooter)) { + NavigationLink(destination: Text(UserText.subscriptionFAQ)) { + SettingsCustomCell(content: { + Text(UserText.subscriptionFAQ) + .daxBodyRegular() + }) } } - .navigationTitle(UserText.settingsPProManageSubscription) - .applyInsetGroupedListStyle() - - // Remove subscription - .alert(isPresented: $viewModel.shouldDisplayRemovalNotice) { - Alert( - title: Text(UserText.subscriptionRemoveFromDeviceConfirmTitle), - message: Text(UserText.subscriptionRemoveFromDeviceConfirmText), - primaryButton: .cancel(Text(UserText.subscriptionRemoveCancel)) { - }, - secondaryButton: .destructive(Text(UserText.subscriptionRemove)) { - viewModel.removeSubscription() - presentationMode.wrappedValue.dismiss() - } - ) + } + .navigationTitle(UserText.settingsPProManageSubscription) + .applyInsetGroupedListStyle() + + .onChange(of: viewModel.shouldDismissView) { value in + if value { + dismiss() } } + + // Remove subscription + .alert(isPresented: $viewModel.shouldDisplayRemovalNotice) { + Alert( + title: Text(UserText.subscriptionRemoveFromDeviceConfirmTitle), + message: Text(UserText.subscriptionRemoveFromDeviceConfirmText), + primaryButton: .cancel(Text(UserText.subscriptionRemoveCancel)) { + }, + secondaryButton: .destructive(Text(UserText.subscriptionRemove)) { + viewModel.removeSubscription() + presentationMode.wrappedValue.dismiss() + } + ) + } + + .onAppear { + viewModel.fetchAndUpdateSubscriptionDetails() + } + } + + var body: some View { + Group { + if #available(iOS 16.0, *) { + listView + .scrollDisabled(true) + } else { + listView + } + } + .navigationBarTitleDisplayMode(.inline) + + + } + +} +#endif + + +#if SUBSCRIPTION && DEBUG +@available(iOS 15.0, *) + +struct SubscriptionSettingsView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + SubscriptionSettingsView().navigationBarTitleDisplayMode(.inline) + } + // You can customize the preview environment here if needed. + // For example, you can set a specific device, size, or dark mode/light mode. + // .previewDevice(PreviewDevice(rawValue: "iPhone 12")) + // .preferredColorScheme(.dark) + } } #endif diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 3578eb72cf..51e4164f35 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -957,12 +957,13 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsPProDescription = NSLocalizedString("settings.subscription.description", value:"More seamless privacy with three new protections, including:", comment: "Privacy pro description subtext") public static let settingsPProFeatures = NSLocalizedString("settings.subscription.features", value: """ - • VPN (Virtual Private Network) + • VPN • Personal Information Removal • Identity Theft Restoration """, comment: "Privacy pro features list") - public static let settingsPProLearnMore = NSLocalizedString("settings.subscription.learn.more", value: "Learn More", comment: "Learn more button text for privacy pro") + public static let settingsPProLearnMore = NSLocalizedString("settings.subscription.learn.more", value: "Get Privacy Pro", comment: "Get Privacy Pro button text for privacy pro") + public static let settingsPProIHaveASubscription = NSLocalizedString("settings.subscription.existing.subscription", value: "I Have a Subscription", comment: "I have a Subscription button text for privacy pro") public static let settingsPProManageSubscription = NSLocalizedString("settings.subscription.manage", value: "Subscription Settings", comment: "Subscription Settings button text for privacy pro") @@ -1003,21 +1004,30 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionTitle = NSLocalizedString("subscription.title", value: "Privacy Pro", comment: "Navigation bar Title for subscriptions") public static let subscriptionCloseButton = NSLocalizedString("subscription.close", value: "Close", comment: "Navigation Button for closing subscription view") - static func subscriptionInfo(expiration: String) -> String { - let localized = NSLocalizedString("subscription.subscription.active.caption", value: "Your Privacy Pro subscription renews on %@", comment: "Subscription Expiration Data") - return String(format: localized, expiration) + static func subscriptionInfo(status: String, expiration: String) -> String { + let localized = NSLocalizedString("subscription.subscription.active.caption", + value: "Your subscription %@ on %@", + comment: "Subscription Expiration Data. This reads as 'Your subscription (renews or expires) on (date)'") + return String(format: localized, status, expiration) } + + public static let subscriptionRenews = NSLocalizedString("subscription.renews", value: "renews", comment: "text for renewal string") + public static let subscriptionExpires = NSLocalizedString("subscription.expires", value: "expires", comment: "text for expiration string") + public static let subscriptionMonthly = NSLocalizedString("subscription.monthly", value: "Monthly Subscription", comment: "Subscription type") + public static let subscriptionAnnual = NSLocalizedString("subscription.annual", value: "Annual Subscription", comment: "Subscription type") + public static let subscriptionManageDevices = NSLocalizedString("subscription.manage.devices", value: "Manage Devices", comment: "Header for the device management section") public static let subscriptionAddDeviceButton = NSLocalizedString("subscription.add.device.button", value: "Add to Another Device", comment: "Add to another device button") public static let subscriptionRemoveFromDevice = NSLocalizedString("subscription.remove.from.device.button", value: "Remove From This Device", comment: "Remove from this device button") + public static let subscriptionManageTitle = NSLocalizedString("subscription.manage.title", value: "Subscription", comment: "Header for the subscription section") public static let subscriptionManagePlan = NSLocalizedString("subscription.manage.plan", value: "Manage Plan", comment: "Manage Plan header") public static let subscriptionChangePlan = NSLocalizedString("subscription.change.plan", value: "Change Plan Or Billing", comment: "Change plan or billing title") public static let subscriptionHelpAndSupport = NSLocalizedString("subscription.help", value: "Help and support", comment: "Help and support Section header") public static let subscriptionFAQ = NSLocalizedString("subscription.faq", value: "Privacy Pro FAQ", comment: "FAQ Button") - public static let subscriptionFAQFooter = NSLocalizedString("subscription.faq.description", value: "Visit our Privacy Pro help pages for answers to frequently asked questions", comment: "FAQ Description") + public static let subscriptionFAQFooter = NSLocalizedString("subscription.faq.description", value: "Get answers to frequently asked questions about Privacy Pro in our help pages.", comment: "FAQ Description") // Remove subscription confirmation - public static let subscriptionRemoveFromDeviceConfirmTitle = NSLocalizedString("subscription.remove.from.device.title", value: "Remove From This Device?", comment: "Remove from device confirmation dialog title") + public static let subscriptionRemoveFromDeviceConfirmTitle = NSLocalizedString("subscription.remove.from.device.title", value: "Remove from this device?", comment: "Remove from device confirmation dialog title") public static let subscriptionRemoveFromDeviceConfirmText = NSLocalizedString("subscription.remove.from.device.text", value: "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices.", comment: "Remove from device confirmation dialog text") public static let subscriptionRemove = NSLocalizedString("subscription.remove.subscription", value: "Remove Subscription", comment: "Remove subscription button text") public static let subscriptionRemoveCancel = NSLocalizedString("subscription.remove.subscription.cancel", value: "Cancel", comment: "Remove subscription cancel button text") @@ -1026,22 +1036,26 @@ But if you *do* want a peek under the hood, you can find more information about // Subscription Restore public static let subscriptionActivate = NSLocalizedString("subscription.activate", value: "Activate Subscription", comment: "Subscription Activation Window Title") public static let subscriptionActivateTitle = NSLocalizedString("subscription.activate.title", value: "Activate your subscription on this device", comment: "Subscription Activation Title") - public static let subscriptionActivateDescription = NSLocalizedString("subscription.activate.description", value: "Access your Privacy Pro subscription on this device via Apple ID or an email address.", comment: "Subscription Activation Info") + public static let subscriptionActivateDescription = NSLocalizedString("subscription.activate.description", value: "Your subscription is automatically available in DuckDuckGo on any device signed in to your Apple ID.", comment: "Subscription Activation Info") public static let subscriptionActivateAppleID = NSLocalizedString("subscription.activate.appleid", value: "Apple ID", comment: "Apple ID option for activation") public static let subscriptionActivateAppleIDButton = NSLocalizedString("subscription.activate.appleid.button", value: "Restore Purchase", comment: "Button text for restoring purchase via Apple ID") public static let subscriptionActivateAppleIDDescription = NSLocalizedString("subscription.activate.appleid.description", value: "Restore your purchase to activate your subscription on this device.", comment: "Description for Apple ID activation") - public static let subscriptionRestoreAppleID = NSLocalizedString("subscription.activate.restore.apple", value: "Restore", comment: "Restore button title for AppleID") + public static let subscriptionRestoreAppleID = NSLocalizedString("subscription.activate.restore.apple", value: "Restore Purchase", comment: "Restore button title for AppleID") public static let subscriptionActivateEmail = NSLocalizedString("subscription.activate.email", value: "Email", comment: "Email option for activation") public static let subscriptionActivateEmailDescription = NSLocalizedString("subscription.activate.email.description", value: "Use your email to activate your subscription on this device.", comment: "Description for Email activation") - public static let subscriptionRestoreEmail = NSLocalizedString("subscription.activate.restore.email", value: "Enter Email", comment: "Restore button title for Email") + public static let subscriptionAddDeviceEmailDescription = NSLocalizedString("subscription.addDevice.email.description", value: "Add an email address to access your subscription in DuckDuckGo on other devices. We’ll only use this address to verify your subscription.", comment: "Description for Email adding") + public static let subscriptionAddEmailButton = NSLocalizedString("subscription.activate.add.email.button", value: "Add Email", comment: "Restore button title for Email") + public static let subscriptionActivateEmailButton = NSLocalizedString("subscription.activate.email.button", value: "Enter Email", comment: "Restore button title for Email") // Add to other devices (AppleID / Email) public static let subscriptionAddDeviceTitle = NSLocalizedString("subscription.add.device.title", value: "Add Device", comment: "Add to another device view title") public static let subscriptionAddDeviceHeaderTitle = NSLocalizedString("subscription.add.device.header.title", value: "Use your subscription on all your devices", comment: "Add subscription to other device title ") - public static let subscriptionAddDeviceDescription = NSLocalizedString("subscription.add.device.description", value: "Access your Privacy Pro subscription on any of your devices via Apple ID or by adding an email address.", comment: "Subscription Add device Info") + public static let subscriptionAddDeviceDescription = NSLocalizedString("subscription.add.device.description", value: "Access your Privacy Pro subscription on other devices via an email address.", comment: "Subscription Add device Info") public static let subscriptionAvailableInApple = NSLocalizedString("subscription.available.apple", value: "Privacy Pro is available on any device signed in to the same Apple ID.", comment: "Subscription availability message on Apple devices") public static let subscriptionManageEmailResendInstructions = NSLocalizedString("subscription.add.device.resend.instructions", value: "Resend Instructions", comment: "Resend activation instructions button") + public static let subscriptionConfirmTitle = NSLocalizedString("subscription.confirm.title", value: "Are you sure?", comment: "Title for Confirm messages") + public static let subscriptionAlertTitle = NSLocalizedString("subscription.alert.title", value: "", comment: "Title for Alert messages") // Add Email To subscription public static let subscriptionAddEmail = NSLocalizedString("subscription.add.email", value: "Add an email address to activate your subscription on your other devices. We’ll only use this address to verify your subscription.", comment: "Add email to an existing subscription") @@ -1049,7 +1063,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionRestoreAddEmailTitle = NSLocalizedString("subscription.add.email.title", value: "Add Email", comment: "View title for adding email to subscription") // Manage Subscription Email - public static let subscriptionManageEmailDescription = NSLocalizedString("subscription.manage.email.description", value: "You can use this email to activate your subscription on your other devices.", comment: "Description for Email Management options") + public static let subscriptionManageEmailDescription = NSLocalizedString("subscription.manage.email.description", value: "You can use this email to activate your subscription from browser settings in the DuckDuckGo app on your other devices.", comment: "Description for Email Management options") public static let subscriptionManageEmailButton = NSLocalizedString("subscription.activate.manage.email.button", value: "Manage", comment: "Restore button title for Managing Email") public static let subscriptionManageEmailTitle = NSLocalizedString("subscription.activate.manage.email.title", value: "Manage Email", comment: "View Title for managing your email account") public static let subscriptionManageEmailCancelButton = NSLocalizedString("subscription.activate.manage.email.cancel", value: "Cancel", comment: "Button title for cancelling email deletion") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index db79bdb37c..58c83b7d77 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1840,8 +1840,11 @@ But if you *do* want a peek under the hood, you can find more information about /* Privacy pro description subtext */ "settings.subscription.description" = "More seamless privacy with three new protections, including:"; +/* I have a Subscription button text for privacy pro */ +"settings.subscription.existing.subscription" = "I Have a Subscription"; + /* Privacy pro features list */ -"settings.subscription.features" = " • VPN (Virtual Private Network) +"settings.subscription.features" = " • VPN • Personal Information Removal • Identity Theft Restoration"; @@ -1851,8 +1854,8 @@ But if you *do* want a peek under the hood, you can find more information about /* Identity theft restoration cell title for privacy pro */ "settings.subscription.ITR.title" = "Identity Theft Restoration"; -/* Learn more button text for privacy pro */ -"settings.subscription.learn.more" = "Learn More"; +/* Get Privacy Pro button text for privacy pro */ +"settings.subscription.learn.more" = "Get Privacy Pro"; /* Subscription Settings button text for privacy pro */ "settings.subscription.manage" = "Subscription Settings"; @@ -1905,6 +1908,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Subscription Activation Window Title */ "subscription.activate" = "Activate Subscription"; +/* Restore button title for Email */ +"subscription.activate.add.email.button" = "Add Email"; + /* Apple ID option for activation */ "subscription.activate.appleid" = "Apple ID"; @@ -1915,11 +1921,14 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.activate.appleid.description" = "Restore your purchase to activate your subscription on this device."; /* Subscription Activation Info */ -"subscription.activate.description" = "Access your Privacy Pro subscription on this device via Apple ID or an email address."; +"subscription.activate.description" = "Your subscription is automatically available in DuckDuckGo on any device signed in to your Apple ID."; /* Email option for activation */ "subscription.activate.email" = "Email"; +/* Restore button title for Email */ +"subscription.activate.email.button" = "Enter Email"; + /* Description for Email activation */ "subscription.activate.email.description" = "Use your email to activate your subscription on this device."; @@ -1936,10 +1945,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.activate.manage.email.title" = "Manage Email"; /* Restore button title for AppleID */ -"subscription.activate.restore.apple" = "Restore"; - -/* Restore button title for Email */ -"subscription.activate.restore.email" = "Enter Email"; +"subscription.activate.restore.apple" = "Restore Purchase"; /* Subscription Activation Title */ "subscription.activate.title" = "Activate your subscription on this device"; @@ -1948,7 +1954,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.add.device.button" = "Add to Another Device"; /* Subscription Add device Info */ -"subscription.add.device.description" = "Access your Privacy Pro subscription on any of your devices via Apple ID or by adding an email address."; +"subscription.add.device.description" = "Access your Privacy Pro subscription on other devices via an email address."; /* Add subscription to other device title */ "subscription.add.device.header.title" = "Use your subscription on all your devices"; @@ -1968,6 +1974,15 @@ But if you *do* want a peek under the hood, you can find more information about /* View title for adding email to subscription */ "subscription.add.email.title" = "Add Email"; +/* Description for Email adding */ +"subscription.addDevice.email.description" = "Add an email address to access your subscription in DuckDuckGo on other devices. We’ll only use this address to verify your subscription."; + +/* Title for Alert messages */ +"subscription.alert.title" = "subscription.alert.title"; + +/* Subscription type */ +"subscription.annual" = "Annual Subscription"; + /* Subscription availability message on Apple devices */ "subscription.available.apple" = "Privacy Pro is available on any device signed in to the same Apple ID."; @@ -1980,11 +1995,17 @@ But if you *do* want a peek under the hood, you can find more information about /* Navigation Button for closing subscription view */ "subscription.close" = "Close"; +/* Title for Confirm messages */ +"subscription.confirm.title" = "Are you sure?"; + +/* text for expiration string */ +"subscription.expires" = "expires"; + /* FAQ Button */ "subscription.faq" = "Privacy Pro FAQ"; /* FAQ Description */ -"subscription.faq.description" = "Visit our Privacy Pro help pages for answers to frequently asked questions"; +"subscription.faq.description" = "Get answers to frequently asked questions about Privacy Pro in our help pages."; /* Cancel action for the existing subscription dialog */ "subscription.found.cancel" = "Cancel"; @@ -2010,6 +2031,12 @@ But if you *do* want a peek under the hood, you can find more information about /* Manage Plan header */ "subscription.manage.plan" = "Manage Plan"; +/* Header for the subscription section */ +"subscription.manage.title" = "Subscription"; + +/* Subscription type */ +"subscription.monthly" = "Monthly Subscription"; + /* Alert content for not found subscription */ "subscription.notFound.alert.message" = "The subscription associated with this Apple ID is no longer active."; @@ -2053,7 +2080,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.remove.from.device.text" = "You will no longer be able to access your Privacy Pro subscription on this device. This will not cancel your subscription, and it will remain active on your other devices."; /* Remove from device confirmation dialog title */ -"subscription.remove.from.device.title" = "Remove From This Device?"; +"subscription.remove.from.device.title" = "Remove from this device?"; /* Remove subscription button text */ "subscription.remove.subscription" = "Remove Subscription"; @@ -2061,6 +2088,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Remove subscription cancel button text */ "subscription.remove.subscription.cancel" = "Cancel"; +/* text for renewal string */ +"subscription.renews" = "renews"; + /* Alert button text for restored purchase alert */ "subscription.restore.success.alert.button" = "OK"; @@ -2070,8 +2100,8 @@ But if you *do* want a peek under the hood, you can find more information about /* Alert title for restored purchase */ "subscription.restore.success.alert.title" = "You’re all set."; -/* Subscription Expiration Data */ -"subscription.subscription.active.caption" = "Your Privacy Pro subscription renews on %@"; +/* Subscription Expiration Data. This reads as 'Your subscription (renews or expires) on (date)' */ +"subscription.subscription.active.caption" = "Your subscription %1$@ on %2$@"; /* Navigation bar Title for subscriptions */ "subscription.title" = "Privacy Pro"; diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 40ce86837d..6b7ad1e7f1 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 40ce86837def0adbf558f00ed0531ab4df5839a8 +Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 From 56aa8a0894189e498827507fad14769695218caa Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 28 Feb 2024 17:06:11 +0600 Subject: [PATCH 058/245] move swiftlint plugin to apple-toolbox (#2507) Task/Issue URL: https://app.asana.com/0/1201037661562251/1206699853968688/f macOS PR:duckduckgo/macos-browser#2279 BSK PR: duckduckgo/BrowserServicesKit#680 apple-toolbox PR: duckduckgo/apple-toolbox#1 --- DuckDuckGo.xcodeproj/project.pbxproj | 137 +++++++++--------- .../xcshareddata/swiftpm/Package.resolved | 15 +- LocalPackages/DuckUI/Package.swift | 4 +- LocalPackages/SyncUI/Package.swift | 6 +- LocalPackages/Waitlist/Package.swift | 10 +- 5 files changed, 87 insertions(+), 85 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1479662e48..218cc17d5b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -5599,7 +5599,7 @@ buildRules = ( ); dependencies = ( - B6080BB52B20B03800B418EF /* PBXTargetDependency */, + B6F997D42B8F381A00476735 /* PBXTargetDependency */, 4B470EE7299C6DFB0086EBDC /* PBXTargetDependency */, ); name = PacketTunnelProvider; @@ -5621,7 +5621,7 @@ buildRules = ( ); dependencies = ( - B6080BBD2B20B05000B418EF /* PBXTargetDependency */, + B6F997DC2B8F383100476735 /* PBXTargetDependency */, 025CCFE82582601C001CD5BB /* PBXTargetDependency */, ); name = FingerprintingUITests; @@ -5641,7 +5641,7 @@ buildRules = ( ); dependencies = ( - B6080BAF2B20B02800B418EF /* PBXTargetDependency */, + B6F997CE2B8F380D00476735 /* PBXTargetDependency */, ); name = ShareExtension; productName = ShareExtension; @@ -5656,7 +5656,6 @@ 0A991F5B239418D100AA1F64 /* Prevent Version Override */, 98B0CE69251C937D003FB601 /* Update Localizable.strings */, 84E3418E1E2F7EFB00BDBA6F /* Sources */, - 8558AA7D20EE3CB200A346E9 /* Swift Lint */, 84E3418F1E2F7EFB00BDBA6F /* Frameworks */, 84E341901E2F7EFB00BDBA6F /* Resources */, F143C2F01E4A4CD400CFDE3A /* Embed Frameworks */, @@ -5668,7 +5667,7 @@ buildRules = ( ); dependencies = ( - B6080BAD2B20B02400B418EF /* PBXTargetDependency */, + B6F997CC2B8F380A00476735 /* PBXTargetDependency */, F143C2EA1E4A4CD400CFDE3A /* PBXTargetDependency */, 8390447520BDCE10006461CD /* PBXTargetDependency */, 85482D932462DCD100EDEDD1 /* PBXTargetDependency */, @@ -5701,7 +5700,7 @@ buildRules = ( ); dependencies = ( - B6080BBB2B20B04D00B418EF /* PBXTargetDependency */, + B6F997DA2B8F382E00476735 /* PBXTargetDependency */, 84E341A81E2F7EFB00BDBA6F /* PBXTargetDependency */, ); name = UnitTests; @@ -5726,7 +5725,7 @@ buildRules = ( ); dependencies = ( - B6080BB32B20B03400B418EF /* PBXTargetDependency */, + B6F997D22B8F381600476735 /* PBXTargetDependency */, 85DF714924F7FE6100C89288 /* PBXTargetDependency */, ); name = WidgetsExtension; @@ -5748,7 +5747,7 @@ buildRules = ( ); dependencies = ( - B6080BB12B20B02B00B418EF /* PBXTargetDependency */, + B6F997D02B8F381100476735 /* PBXTargetDependency */, ); name = OpenAction; productName = OpenAction; @@ -5766,7 +5765,7 @@ buildRules = ( ); dependencies = ( - B6080BBF2B20B05300B418EF /* PBXTargetDependency */, + B6F997DE2B8F383400476735 /* PBXTargetDependency */, 85D33FD125C97B6E002B91A6 /* PBXTargetDependency */, ); name = IntegrationTests; @@ -5789,7 +5788,7 @@ buildRules = ( ); dependencies = ( - B6080BB92B20B04A00B418EF /* PBXTargetDependency */, + B6F997D82B8F382900476735 /* PBXTargetDependency */, 85F21DB3210F5E32002631A6 /* PBXTargetDependency */, ); name = AtbUITests; @@ -5811,7 +5810,7 @@ buildRules = ( ); dependencies = ( - B6080BC12B20B05600B418EF /* PBXTargetDependency */, + B6F997E02B8F383700476735 /* PBXTargetDependency */, 9825F9CC293F2DE900F220F2 /* PBXTargetDependency */, ); name = PerformanceTests; @@ -5848,7 +5847,7 @@ buildRules = ( ); dependencies = ( - B6080BB72B20B03B00B418EF /* PBXTargetDependency */, + B6F997D62B8F381D00476735 /* PBXTargetDependency */, ); name = Core; packageProductDependencies = ( @@ -5991,6 +5990,7 @@ 0238E44D29C0FAA100615E30 /* XCRemoteSwiftPackageReference "ios-js-support" */, 4B2754EA29E8C7DF00394032 /* XCRemoteSwiftPackageReference "lottie-ios" */, 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, + B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */, ); productRefGroup = 84E341931E2F7EFB00BDBA6F /* Products */; projectDirPath = ""; @@ -6298,21 +6298,6 @@ shellPath = /bin/sh; shellScript = "# This script copies GRDB.framework to the bundle and signs it\n# It's required because GRDB is not an explicit app dependency\n# and as such it can't be selected in \"Copy Frameworks\" build phase.\n\ngrdb_source_dir=\"${BUILT_PRODUCTS_DIR}/GRDB.framework\"\ngrdb_install_dir=\"${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/GRDB.framework\"\n\n# Remove any existing files in the destination\nrm -rf \"${grdb_install_dir}\"\nmkdir -p \"${grdb_install_dir}\"\n\n# Copy the framework and the Info.plist\ncp -f \"${grdb_source_dir}/GRDB\" \"${grdb_source_dir}/Info.plist\" \"${grdb_install_dir}\"\n\n# Sign the framework directory contents\n/usr/bin/codesign \\\n --force \\\n --sign \"${EXPANDED_CODE_SIGN_IDENTITY}\" \\\n --timestamp\\=none \\\n --preserve-metadata\\=identifier,entitlements,flags \\\n --generate-entitlement-der \"${grdb_install_dir}\"\n"; }; - 8558AA7D20EE3CB200A346E9 /* Swift Lint */ = { - isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; - buildActionMask = 12; - files = ( - ); - inputPaths = ( - ); - name = "Swift Lint"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "./lint.sh --strict\n"; - }; 98B0CE69251C937D003FB601 /* Update Localizable.strings */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -7289,49 +7274,49 @@ target = 84E341911E2F7EFB00BDBA6F /* DuckDuckGo */; targetProxy = 9825F9CD293F2DE900F220F2 /* PBXContainerItemProxy */; }; - B6080BAD2B20B02400B418EF /* PBXTargetDependency */ = { + B6F997CC2B8F380A00476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BAC2B20B02400B418EF /* SwiftLintPlugin */; + productRef = B6F997CB2B8F380A00476735 /* SwiftLintPlugin */; }; - B6080BAF2B20B02800B418EF /* PBXTargetDependency */ = { + B6F997CE2B8F380D00476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BAE2B20B02800B418EF /* SwiftLintPlugin */; + productRef = B6F997CD2B8F380D00476735 /* SwiftLintPlugin */; }; - B6080BB12B20B02B00B418EF /* PBXTargetDependency */ = { + B6F997D02B8F381100476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BB02B20B02B00B418EF /* SwiftLintPlugin */; + productRef = B6F997CF2B8F381100476735 /* SwiftLintPlugin */; }; - B6080BB32B20B03400B418EF /* PBXTargetDependency */ = { + B6F997D22B8F381600476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BB22B20B03400B418EF /* SwiftLintPlugin */; + productRef = B6F997D12B8F381600476735 /* SwiftLintPlugin */; }; - B6080BB52B20B03800B418EF /* PBXTargetDependency */ = { + B6F997D42B8F381A00476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BB42B20B03800B418EF /* SwiftLintPlugin */; + productRef = B6F997D32B8F381A00476735 /* SwiftLintPlugin */; }; - B6080BB72B20B03B00B418EF /* PBXTargetDependency */ = { + B6F997D62B8F381D00476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BB62B20B03B00B418EF /* SwiftLintPlugin */; + productRef = B6F997D52B8F381D00476735 /* SwiftLintPlugin */; }; - B6080BB92B20B04A00B418EF /* PBXTargetDependency */ = { + B6F997D82B8F382900476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BB82B20B04A00B418EF /* SwiftLintPlugin */; + productRef = B6F997D72B8F382900476735 /* SwiftLintPlugin */; }; - B6080BBB2B20B04D00B418EF /* PBXTargetDependency */ = { + B6F997DA2B8F382E00476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BBA2B20B04D00B418EF /* SwiftLintPlugin */; + productRef = B6F997D92B8F382E00476735 /* SwiftLintPlugin */; }; - B6080BBD2B20B05000B418EF /* PBXTargetDependency */ = { + B6F997DC2B8F383100476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BBC2B20B05000B418EF /* SwiftLintPlugin */; + productRef = B6F997DB2B8F383100476735 /* SwiftLintPlugin */; }; - B6080BBF2B20B05300B418EF /* PBXTargetDependency */ = { + B6F997DE2B8F383400476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BBE2B20B05300B418EF /* SwiftLintPlugin */; + productRef = B6F997DD2B8F383400476735 /* SwiftLintPlugin */; }; - B6080BC12B20B05600B418EF /* PBXTargetDependency */ = { + B6F997E02B8F383700476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - productRef = B6080BC02B20B05600B418EF /* SwiftLintPlugin */; + productRef = B6F997DF2B8F383700476735 /* SwiftLintPlugin */; }; F143C2EA1E4A4CD400CFDE3A /* PBXTargetDependency */ = { isa = PBXTargetDependency; @@ -9936,7 +9921,15 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 111.0.2; + version = 112.0.0; + }; + }; + B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/duckduckgo/apple-toolbox.git"; + requirement = { + kind = exactVersion; + version = 1.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { @@ -10087,59 +10080,59 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; - B6080BAC2B20B02400B418EF /* SwiftLintPlugin */ = { + B6F997CB2B8F380A00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BAE2B20B02800B418EF /* SwiftLintPlugin */ = { + B6F997CD2B8F380D00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BB02B20B02B00B418EF /* SwiftLintPlugin */ = { + B6F997CF2B8F381100476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BB22B20B03400B418EF /* SwiftLintPlugin */ = { + B6F997D12B8F381600476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BB42B20B03800B418EF /* SwiftLintPlugin */ = { + B6F997D32B8F381A00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BB62B20B03B00B418EF /* SwiftLintPlugin */ = { + B6F997D52B8F381D00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BB82B20B04A00B418EF /* SwiftLintPlugin */ = { + B6F997D72B8F382900476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BBA2B20B04D00B418EF /* SwiftLintPlugin */ = { + B6F997D92B8F382E00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BBC2B20B05000B418EF /* SwiftLintPlugin */ = { + B6F997DB2B8F383100476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BBE2B20B05300B418EF /* SwiftLintPlugin */ = { + B6F997DD2B8F383400476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; - B6080BC02B20B05600B418EF /* SwiftLintPlugin */ = { + B6F997DF2B8F383700476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; - package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; productName = "plugin:SwiftLintPlugin"; }; C14882EC27F211A000D59F0C /* SwiftSoup */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1eeff22c57..e3003e9abe 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,14 @@ { "pins" : [ + { + "identity" : "apple-toolbox", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/apple-toolbox.git", + "state" : { + "revision" : "e3dc4faf70ca09718a2d20d5c47b449389e8c153", + "version" : "1.0.0" + } + }, { "identity" : "bloom_cpp", "kind" : "remoteSourceControl", @@ -14,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "04c35220aa94bd005171086acccadd677400e7d5", - "version" : "111.0.2" + "revision" : "c0921ae979b5bcb5817026f9d34be180f0d7f5cc", + "version" : "112.0.0" } }, { @@ -156,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 0ac99830a6..79df959eeb 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), ], targets: [ .target( @@ -40,7 +40,7 @@ let package = Package( swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) ], - plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")] + plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")] ) ] ) diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 4769fb0741..51c9b26b91 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,8 +33,8 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), - .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") + .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), ], targets: [ .target( @@ -46,7 +46,7 @@ let package = Package( swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) ], - plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")] + plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")] ) ] ) diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 0e1585dd42..f3a3951ba5 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,8 +15,8 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), - .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") + .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), ], targets: [ .target( @@ -25,7 +25,7 @@ let package = Package( swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) ], - plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")] + plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")] ), .target( name: "WaitlistMocks", @@ -33,12 +33,12 @@ let package = Package( swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) ], - plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")] + plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")] ), .testTarget( name: "WaitlistTests", dependencies: ["Waitlist", "WaitlistMocks"], - plugins: [.plugin(name: "SwiftLintPlugin", package: "BrowserServicesKit")] + plugins: [.plugin(name: "SwiftLintPlugin", package: "apple-toolbox")] ) ] ) From 155029edabbbcd8db205f93d29db9efa52e75040 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Wed, 28 Feb 2024 16:00:01 +0000 Subject: [PATCH 059/245] rename cookie and ensure timeout does not fire prematurely (#2509) --- Core/PixelEvent.swift | 2 +- Core/WebCacheManager.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 7261d5bafa..b1766ba89c 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -899,7 +899,7 @@ extension Pixel.Event { case .blankOverlayNotDismissed: return "m_d_ovs" - case .cookieDeletionTimedOut: return "m_d_csto" + case .cookieDeletionTimedOut: return "m_debug_cookie-clearing-timeout" case .cookieDeletionLeftovers: return "m_cookie_deletion_leftovers" case .cachedTabPreviewsExceedsTabCount: return "m_d_tpetc" diff --git a/Core/WebCacheManager.swift b/Core/WebCacheManager.swift index 2ea7a18984..bfaa47d0dd 100644 --- a/Core/WebCacheManager.swift +++ b/Core/WebCacheManager.swift @@ -132,6 +132,7 @@ extension WebCacheManager { private func legacyDataClearing() async -> [HTTPCookie]? { let timeoutTask = Task.detached { + try? await Task.sleep(interval: 5.0) if !Task.isCancelled { Pixel.fire(pixel: .cookieDeletionTimedOut, withAdditionalParameters: [ PixelParameters.clearWebDataTimedOut: "1" From ef552572d3bdcae842da915257c15637370d6a55 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 28 Feb 2024 17:00:27 +0100 Subject: [PATCH 060/245] Fixes a crash in the VPN (#2513) Task/Issue URL: https://app.asana.com/0/414235014887631/1206703155649003/f Tech Design URL: CC: macOS PR: https://github.com/duckduckgo/macos-browser/pull/2282 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/685 ## Description Fixes a crash in the VPN. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1479662e48..1e234684cf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9936,7 +9936,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 111.0.2; + version = "111.1.1-1"; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1eeff22c57..8a344534cb 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "04c35220aa94bd005171086acccadd677400e7d5", - "version" : "111.0.2" + "revision" : "68b7b5ae15073b16092f0fb3db218a4d11e80a06", + "version" : "111.1.1-1" } }, { diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 0ac99830a6..e443ed6c9b 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.1.1-1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 4769fb0741..ceab19f0ca 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -33,7 +33,7 @@ let package = Package( ], dependencies: [ .package(path: "../DuckUI"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.1.1-1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index 0e1585dd42..d01feb3d2c 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -15,7 +15,7 @@ let package = Package( targets: ["Waitlist", "WaitlistMocks"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.0.2"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "111.1.1-1"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0") ], targets: [ From 0bf3e8f4d03caf1de5002404cb1ce47a34b979ca Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:08:02 -0500 Subject: [PATCH 061/245] Prevent duplicate reporting on VPN feedback form (#2511) --- DuckDuckGo/Feedback/VPNFeedbackFormView.swift | 16 +++------------- DuckDuckGo/UserText.swift | 3 --- DuckDuckGo/en.lproj/Localizable.strings | 11 +---------- 3 files changed, 4 insertions(+), 26 deletions(-) diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index 9e666dccc8..c1ea9968bf 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -81,7 +81,6 @@ struct VPNFeedbackFormCategoryView: View { struct VPNFeedbackFormView: View { @ObservedObject var viewModel: VPNFeedbackFormViewModel @Environment(\.dismiss) private var dismiss - @State private var showsError = false @FocusState private var isTextEditorFocused: Bool var onDismiss: () -> Void @@ -90,11 +89,6 @@ struct VPNFeedbackFormView: View { configuredForm() .applyBackground() .navigationTitle(UserText.netPStatusViewShareFeedback) - .alert(isPresented: $showsError) { - Alert(title: Text(UserText.vpnFeedbackFormErrorTitle), - message: Text(UserText.vpnFeedbackFormErrorMessage), - dismissButton: .default(Text(UserText.vpnFeedbackFormErrorAction))) - } } @ViewBuilder @@ -204,14 +198,10 @@ struct VPNFeedbackFormView: View { private func submitButton() -> some View { Button { Task { - let success = await viewModel.process() - if success { - dismiss() - onDismiss() - } else { - showsError = true - } + _ = await viewModel.process() } + dismiss() + onDismiss() } label: { Text(UserText.vpnFeedbackFormButtonSubmit) .daxButton() diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 3578eb72cf..8b18440d45 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -657,9 +657,6 @@ In addition to the details entered into this form, your app issue report will co static let vpnFeedbackFormButtonSubmitting = NSLocalizedString("vpn.feedback-form.button.submitting", value: "Submitting…", comment: "Title for the Submitting state of the VPN feedback form") static let vpnFeedbackFormSubmittedMessage = NSLocalizedString("vpn.feedback-form.submitted.message", value: "Thank You! Feedback submitted.", comment: "Toast message when the VPN feedback form is submitted successfully") - static let vpnFeedbackFormErrorTitle = NSLocalizedString("vpn.feedback-form.error.title", value: "Error", comment: "Title for the alert when the VPN feedback form can't be submitted") - static let vpnFeedbackFormErrorMessage = NSLocalizedString("vpn.feedback-form.error.message", value: "Failed to share your feedback. Please try again.", comment: "Message for the alert when the VPN feedback form can't be submitted") - static let vpnFeedbackFormErrorAction = NSLocalizedString("vpn.feedback-form.error.action", value: "OK", comment: "Action title for the alert when the VPN feedback form can't be submitted") // MARK: Notifications diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index db79bdb37c..2445871097 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2005,7 +2005,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.manage.devices" = "Manage Devices"; /* Description for Email Management options */ -"subscription.manage.email.description" = "You can use this email to activate your subscription on your other devices."; +"subscription.manage.email.description" = "You can use this email to activate your subscription from browser settings in the DuckDuckGo app on your other devices."; /* Manage Plan header */ "subscription.manage.plan" = "Manage Plan"; @@ -2232,15 +2232,6 @@ But if you *do* want a peek under the hood, you can find more information about /* Title for the 'unable to install' category of the VPN feedback form */ "vpn.feedback-form.category.unable-to-install" = "Unable to install VPN"; -/* Action title for the alert when the VPN feedback form can't be submitted */ -"vpn.feedback-form.error.action" = "OK"; - -/* Message for the alert when the VPN feedback form can't be submitted */ -"vpn.feedback-form.error.message" = "Failed to share your feedback. Please try again."; - -/* Title for the alert when the VPN feedback form can't be submitted */ -"vpn.feedback-form.error.title" = "Error"; - /* Title for the feedback sent view description of the VPN feedback form */ "vpn.feedback-form.sending-confirmation.description" = "Your feedback will help us improve the\nDuckDuckGo VPN."; From 062f77bd0d821c1c9312b752ab279c867e7ed7d5 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:10:25 -0500 Subject: [PATCH 062/245] Release 7.110.0-1 (#2515) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1e234684cf..8f41b35304 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8161,7 +8161,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8198,7 +8198,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8290,7 +8290,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8318,7 +8318,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8468,7 +8468,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8494,7 +8494,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8559,7 +8559,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8594,7 +8594,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8628,7 +8628,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8659,7 +8659,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8946,7 +8946,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8977,7 +8977,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9006,7 +9006,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9040,7 +9040,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9071,7 +9071,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9104,11 +9104,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9346,7 +9346,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9373,7 +9373,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9406,7 +9406,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9444,7 +9444,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9480,7 +9480,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9515,11 +9515,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9693,11 +9693,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9726,10 +9726,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From a3960fe38da47ceaacf38c54f2d1ff9c2a42c57b Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Thu, 29 Feb 2024 12:37:27 +0000 Subject: [PATCH 063/245] Update BSK, bump C-S-S to 5.0 (#2512) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 218cc17d5b..4c0ad5d7bc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9921,7 +9921,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 112.0.0; + version = 113.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e3003e9abe..441f2a84c1 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "c0921ae979b5bcb5817026f9d34be180f0d7f5cc", - "version" : "112.0.0" + "revision" : "f903ffcbc51e85ac262c355b56726e3387957a80", + "version" : "113.0.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "36ddba2cbac52a41b9a9275af06d32fa8a56d2d7", - "version" : "4.64.0" + "revision" : "a3690b7666a3617693383d948cb492513f6aa569", + "version" : "5.0.0" } }, { From 8b901e8a9e56878582fc29ed2fc78466bf697fc3 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 29 Feb 2024 18:43:29 +0100 Subject: [PATCH 064/245] 14. Implemented Error handlers for all subscription scenarios (#2508) Task/Issue URL: https://app.asana.com/0/414235014887631/1206707680638882/f Description: This adds different error-handling 'handles' to attach pixels and events and streamlines the overall error-handling flow in the UI --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +- DuckDuckGo/AppDelegate.swift | 4 +- ...scriptionPagesUseSubscriptionFeature.swift | 163 ++++++++++++------ .../SubscriptionEmailViewModel.swift | 30 ++++ .../ViewModel/SubscriptionFlowViewModel.swift | 72 ++++++-- .../SubscriptionRestoreViewModel.swift | 43 ++++- .../Views/SubscriptionEmailView.swift | 19 ++ .../Views/SubscriptionFlowView.swift | 48 ++++-- .../Views/SubscriptionRestoreView.swift | 13 +- DuckDuckGo/UserText.swift | 9 +- DuckDuckGo/en.lproj/Localizable.strings | 17 +- 12 files changed, 338 insertions(+), 88 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2caf899ac6..5448f63005 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9921,7 +9921,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 113.0.0; + version = 113.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 441f2a84c1..f69b61dde0 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "f903ffcbc51e85ac262c355b56726e3387957a80", - "version" : "113.0.0" + "revision" : "7f5c89edfdf38cec173f125f46bacad43960d2d4", + "version" : "113.1.0" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 01bcadace1..7c020807ec 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -364,7 +364,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if SUBSCRIPTION private func setupSubscriptionsEnvironment() { - Task { SubscriptionPurchaseEnvironment.current = .appStore + Task { + SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging + SubscriptionPurchaseEnvironment.current = .appStore await AccountManager().checkSubscriptionState() } } diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index e7013f52cd..0af6a2d15f 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -67,13 +67,29 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec struct FeatureSelection: Codable { let feature: String } + + enum UseSubscriptionError: Error { + case purchaseFailed, + missingEntitlements, + failedToGetSubscriptionOptions, + failedToSetSubscription, + failedToRestoreFromEmail, + failedToRestoreFromEmailSubscriptionInactive, + failedToRestorePastPurchase, + subscriptionNotFound, + subscriptionExpired, + hasActiveSubscription, + cancelledByUser, + generalError + } - @Published var transactionStatus: SubscriptionTransactionStatus = .idle - @Published var hasActiveSubscription = false - @Published var purchaseError: AppStorePurchaseFlow.Error? - @Published var activateSubscription: Bool = false - @Published var emailActivationComplete: Bool = false + // Transaction Status and erros are observed from ViewModels to handle errors in the UI + @Published private(set) var transactionStatus: SubscriptionTransactionStatus = .idle + @Published private(set) var transactionError: UseSubscriptionError? + + @Published private(set) var activateSubscription: Bool = false @Published var selectedFeature: FeatureSelection? + @Published var emailActivationComplete: Bool = false weak var broker: UserScriptMessageBroker? @@ -115,48 +131,58 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // swiftlint:enable nesting - // Manage transation in progress flag - private func withTransactionInProgress(_ work: () async throws -> T) async rethrows -> T { - transactionStatus = transactionStatus + // Manage transaction in progress flag + private func withTransactionInProgress(_ work: () async -> T) async -> T { + setTransactionStatus(transactionStatus) defer { - transactionStatus = .idle + setTransactionStatus(.idle) } - return try await work() + return await work() } private func resetSubscriptionFlow() { - hasActiveSubscription = false - purchaseError = nil + setTransactionError(nil) + } + + private func setTransactionError(_ error: UseSubscriptionError?) { + transactionError = error + } + + private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { + transactionStatus = status } - func getSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + + // MARK: Broker Methods (Called from WebView via UserScripts) + func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { let authToken = AccountManager().authToken ?? Constants.empty return Subscription(token: authToken) } - func getSubscriptionOptions(params: Any, original: WKScriptMessage) async throws -> Encodable? { + func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { await withTransactionInProgress { - transactionStatus = .purchasing + setTransactionStatus(.purchasing) resetSubscriptionFlow() switch await AppStorePurchaseFlow.subscriptionOptions() { case .success(let subscriptionOptions): return subscriptionOptions case .failure: - os_log(.info, log: .subscription, "Failed to obtain subscription options") + os_log(.error, log: .subscription, "Failed to obtain subscription options") + setTransactionError(.failedToGetSubscriptionOptions) return nil } } } - func subscriptionSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + func subscriptionSelected(params: Any, original: WKScriptMessage) async -> Encodable? { await withTransactionInProgress { - - transactionStatus = .purchasing + setTransactionError(nil) + setTransactionStatus(.purchasing) resetSubscriptionFlow() struct SubscriptionSelection: Decodable { @@ -171,7 +197,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // Check for active subscriptions if await PurchaseManager.hasActiveSubscription() { - hasActiveSubscription = true + setTransactionError(.hasActiveSubscription) return nil } @@ -180,27 +206,35 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { case .success: break - case .failure: - purchaseError = .purchaseFailed + case .failure(let error): + + switch error { + case .cancelledByUser: + setTransactionError(.cancelledByUser) + default: + setTransactionError(.purchaseFailed) + } originalMessage = original + setTransactionStatus(.idle) return nil } - transactionStatus = .polling + setTransactionStatus(.polling) switch await AppStorePurchaseFlow.completeSubscriptionPurchase() { case .success(let purchaseUpdate): await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure: - purchaseError = .missingEntitlements + setTransactionError(.missingEntitlements) await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) } return nil } } - func setSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + func setSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { guard let subscriptionValues: SubscriptionValues = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionValues") + setTransactionError(.generalError) return nil } @@ -211,32 +245,53 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec accountManager.storeAuthToken(token: authToken) accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) } else { - os_log(.info, log: .subscription, "Failed to obtain subscription options") + os_log(.error, log: .subscription, "Failed to obtain subscription options") + setTransactionError(.failedToSetSubscription) } return nil } - func backToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { let accountManager = AccountManager() if let accessToken = accountManager.accessToken, case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) - emailActivationComplete = true + switch await SubscriptionService.getSubscriptionDetails(token: accessToken) { + + // If the account is not active, display an error and logout + case .success(let response) where !response.isSubscriptionActive: + setTransactionError(.failedToRestoreFromEmailSubscriptionInactive) + accountManager.signOut() + return nil + + case .success: + + // Store the account data and mark as active + accountManager.storeAccount(token: accessToken, + email: accountDetails.email, + externalID: accountDetails.externalID) + emailActivationComplete = true + + case .failure: + os_log(.error, log: .subscription, "Failed to restore subscription from Email") + setTransactionError(.failedToRestoreFromEmail) + } } else { - os_log(.info, log: .subscription, "Failed to restore subscription from Email") + os_log(.error, log: .subscription, "General error. Could not get account Details") + setTransactionError(.generalError) } return nil } - func activateSubscription(params: Any, original: WKScriptMessage) async throws -> Encodable? { + func activateSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { activateSubscription = true return nil } - func featureSelected(params: Any, original: WKScriptMessage) async throws -> Encodable? { + func featureSelected(params: Any, original: WKScriptMessage) async -> Encodable? { guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") + setTransactionError(.generalError) return nil } selectedFeature = featureSelection @@ -244,8 +299,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec return nil } - // MARK: Push actions - + // MARK: Push actions (Push Data back to WebViews) enum SubscribeActionName: String { case onPurchaseUpdate } @@ -257,28 +311,40 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func pushAction(method: SubscribeActionName, webView: WKWebView, params: Encodable) { let broker = UserScriptMessageBroker(context: SubscriptionPagesUserScript.context, requiresRunInPageContentWorld: true ) - broker.push(method: method.rawValue, params: params, for: self, into: webView) } - func restoreAccountFromAppStorePurchase() async -> Bool { + + // MARK: Native methods - Called from ViewModels + func restoreAccountFromAppStorePurchase() async throws { + setTransactionStatus(.restoring) - await withTransactionInProgress { - transactionStatus = .restoring - switch await AppStoreRestoreFlow.restoreAccountFromPastPurchase() { - case .success: - return true - case .failure: - return false - } + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + switch result { + case .success: + setTransactionStatus(.idle) + case .failure(let error): + let mappedError = mapAppStoreRestoreErrorToTransactionError(error) + setTransactionStatus(.idle) + throw mappedError + } + } + + // MARK: Utility Methods + func mapAppStoreRestoreErrorToTransactionError(_ error: AppStoreRestoreFlow.Error) -> UseSubscriptionError { + switch error { + case .subscriptionExpired: + return .subscriptionExpired + case .missingAccountOrTransactions: + return .subscriptionNotFound + default: + return .failedToRestorePastPurchase } - } func cleanup() { - transactionStatus = .idle - hasActiveSubscription = false - purchaseError = nil + setTransactionStatus(.idle) + setTransactionError(nil) activateSubscription = false emailActivationComplete = false selectedFeature = nil @@ -286,4 +352,5 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } } + #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index d519251bc7..52d746ec87 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -37,6 +37,8 @@ final class SubscriptionEmailViewModel: ObservableObject { @Published var shouldReloadWebView = false @Published var activateSubscription = false @Published var managingSubscriptionEmail = false + @Published var transactionError: SubscriptionRestoreError? + @Published var shouldDisplayInactiveError: Bool = false var webViewModel: AsyncHeadlessWebViewViewModel private static let allowedDomains = [ @@ -45,6 +47,12 @@ final class SubscriptionEmailViewModel: ObservableObject { "duosecurity.com", ] + enum SubscriptionRestoreError: Error { + case failedToRestoreFromEmail, + subscriptionExpired, + generalError + } + private var cancellables = Set() init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), @@ -82,6 +90,28 @@ final class SubscriptionEmailViewModel: ObservableObject { } } .store(in: &cancellables) + + subFeature.$transactionError + .receive(on: DispatchQueue.main) + .removeDuplicates() + .sink { [weak self] value in + guard let strongSelf = self else { return } + if let value { + strongSelf.handleTransactionError(error: value) + } + } + .store(in: &cancellables) + } + + private func handleTransactionError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { + switch error { + + case .subscriptionExpired: + transactionError = .subscriptionExpired + default: + transactionError = .generalError + } + shouldDisplayInactiveError = true } private func completeActivation() { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 4afbdae45b..623d191afb 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -49,6 +49,18 @@ final class SubscriptionFlowViewModel: ObservableObject { static let itr = "identity-theft-restoration" static let dbp = "personal-information-removal" } + + enum SubscriptionPurchaseError: Error { + case purchaseFailed, + missingEntitlements, + failedToGetSubscriptionOptions, + failedToSetSubscription, + failedToRestorePastPurchase, + subscriptionExpired, + hasActiveSubscription, + cancelledByUser, + generalError + } // Published properties @Published var hasActiveSubscription = false @@ -59,6 +71,7 @@ final class SubscriptionFlowViewModel: ObservableObject { @Published var shouldShowNavigationBar: Bool = false @Published var selectedFeature: SettingsViewModel.SettingsSection? @Published var canNavigateBack: Bool = false + @Published var transactionError: SubscriptionPurchaseError? private static let allowedDomains = [ "duckduckgo.com", @@ -95,14 +108,7 @@ final class SubscriptionFlowViewModel: ObservableObject { } } .store(in: &cancellables) - - subFeature.$hasActiveSubscription - .receive(on: DispatchQueue.main) - .sink { [weak self] value in - self?.hasActiveSubscription = value - } - .store(in: &cancellables) - + subFeature.$activateSubscription .receive(on: DispatchQueue.main) .sink { [weak self] value in @@ -131,15 +137,51 @@ final class SubscriptionFlowViewModel: ObservableObject { } .store(in: &cancellables) + + subFeature.$transactionError + .receive(on: DispatchQueue.main) + .removeDuplicates() + .sink { [weak self] value in + guard let strongSelf = self else { return } + if let value { + strongSelf.handleTransactionError(error: value) + } + } + .store(in: &cancellables) } + private func handleTransactionError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { + switch error { + + case .purchaseFailed: + transactionError = .purchaseFailed + case .missingEntitlements: + transactionError = .missingEntitlements + case .failedToGetSubscriptionOptions: + transactionError = .failedToGetSubscriptionOptions + case .failedToSetSubscription: + transactionError = .failedToSetSubscription + case .failedToRestorePastPurchase: + transactionError = .failedToRestorePastPurchase + case .subscriptionExpired: + transactionError = .subscriptionExpired + case .hasActiveSubscription: + transactionError = .hasActiveSubscription + case .cancelledByUser: + transactionError = nil + default: + transactionError = .generalError + } + } + private func setupWebViewObservers() async { webViewModel.$scrollPosition .receive(on: DispatchQueue.main) .sink { [weak self] value in + guard let strongSelf = self else { return } DispatchQueue.main.async { - self?.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold + strongSelf.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold } } .store(in: &cancellables) @@ -186,13 +228,17 @@ final class SubscriptionFlowViewModel: ObservableObject { cancellables.removeAll() } + @MainActor func restoreAppstoreTransaction() { + transactionError = nil Task { - if await subFeature.restoreAccountFromAppStorePurchase() { - await disableGoBack() + do { + try await subFeature.restoreAccountFromAppStorePurchase() + disableGoBack() await webViewModel.navigationCoordinator.reload() - } else { - await MainActor.run { + } catch let error { + if let specificError = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError { + handleTransactionError(error: specificError) } } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 4fca7a32d8..5ad61c5b80 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -32,9 +32,10 @@ final class SubscriptionRestoreViewModel: ObservableObject { let purchaseManager: PurchaseManager let accountManager: AccountManager var isAddingDevice: Bool + private var cancellables = Set() enum SubscriptionActivationResult { - case unknown, activated, notFound, error + case unknown, activated, expired, notFound, error } @Published var transactionStatus: SubscriptionTransactionStatus = .idle @@ -59,6 +60,35 @@ final class SubscriptionRestoreViewModel: ObservableObject { if accountManager.isUserAuthenticated { isAddingDevice = true } + Task { await setupTransactionObserver() } + } + + private func setupTransactionObserver() async { + + subFeature.$transactionStatus + .receive(on: DispatchQueue.main) + .sink { [weak self] status in + guard let strongSelf = self else { return } + Task { + await strongSelf.setTransactionStatus(status) + } + } + .store(in: &cancellables) + + } + + @MainActor + private func handleRestoreError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { + switch error { + case .failedToRestorePastPurchase: + activationResult = .notFound + case .subscriptionExpired: + activationResult = .expired + case .subscriptionNotFound: + activationResult = .notFound + default: + activationResult = .error + } } @MainActor @@ -69,14 +99,15 @@ final class SubscriptionRestoreViewModel: ObservableObject { @MainActor func restoreAppstoreTransaction() { Task { - transactionStatus = .restoring activationResult = .unknown - if await subFeature.restoreAccountFromAppStorePurchase() { + do { + try await subFeature.restoreAccountFromAppStorePurchase() activationResult = .activated - } else { - activationResult = .notFound + } catch let error { + if let specificError = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError { + handleRestoreError(error: specificError) + } } - transactionStatus = .idle } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 50811f81a6..d720aa434f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -29,6 +29,7 @@ struct SubscriptionEmailView: View { @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding @State private var isActive: Bool = false @State var isAddingDevice = false + @State var shouldDisplayInactiveError = false var body: some View { ZStack { @@ -37,10 +38,22 @@ struct SubscriptionEmailView: View { .background() } } + + .alert(isPresented: $shouldDisplayInactiveError) { + Alert( + title: Text(UserText.subscriptionRestoreEmailInactiveTitle), + message: Text(UserText.subscriptionRestoreEmailInactiveMessage), + dismissButton: .default(Text(UserText.actionOK)) { + dismiss() + } + ) + } + .onAppear { viewModel.loadURL() } + .onChange(of: viewModel.activateSubscription) { active in if active { // If updating email, just go back @@ -52,7 +65,13 @@ struct SubscriptionEmailView: View { } } } + + .onChange(of: viewModel.shouldDisplayInactiveError) { _ in + shouldDisplayInactiveError = true + } .navigationTitle(viewModel.viewTitle) } + + } #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 1063e43854..0637e2c18f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -27,9 +27,10 @@ struct SubscriptionFlowView: View { @Environment(\.dismiss) var dismiss @StateObject var viewModel = SubscriptionFlowViewModel() - @State private var isAlertVisible = false @State private var shouldShowNavigationBar = false @State private var isActive: Bool = false + @State private var transactionError: SubscriptionFlowViewModel.SubscriptionPurchaseError? + @State private var shouldPresentError: Bool = false enum Constants { static let daxLogo = "Home" @@ -118,12 +119,6 @@ struct SubscriptionFlowView: View { } } - .onChange(of: viewModel.hasActiveSubscription) { result in - if result { - isAlertVisible = true - } - } - .onChange(of: viewModel.shouldDismissView) { result in if result { dismiss() @@ -136,6 +131,13 @@ struct SubscriptionFlowView: View { viewModel.userTappedRestoreButton = false } + .onChange(of: viewModel.transactionError) { value in + if value != nil { + shouldPresentError = true + } + transactionError = value + } + .onAppear(perform: { setUpAppearances() Task { await viewModel.initializeViewData() } @@ -153,24 +155,45 @@ struct SubscriptionFlowView: View { } } }) + + .alert(isPresented: $shouldPresentError) { + getAlert() + } - .alert(isPresented: $isAlertVisible) { + // The trailing close button should be hidden when a transaction is in progress + .navigationBarItems(trailing: viewModel.transactionStatus == .idle + ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } + : nil) + } + + private func getAlert() -> Alert { + + switch transactionError { + + case .hasActiveSubscription: Alert( title: Text(UserText.subscriptionFoundTitle), message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { + viewModel.transactionError = nil }, secondaryButton: .default(Text(UserText.subscriptionFoundRestore)) { viewModel.restoreAppstoreTransaction() } ) + default: + Alert( + title: Text(UserText.subscriptionAppStoreErrorTitle), + message: Text(UserText.subscriptionAppStoreErrorMessage), + dismissButton: .cancel(Text(UserText.actionOK)) { + Task { await viewModel.initializeViewData() } + } + ) } - // The trailing close button should be hidden when a transaction is in progress - .navigationBarItems(trailing: viewModel.transactionStatus == .idle - ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } - : nil) + } + @ViewBuilder private var webView: some View { @@ -191,6 +214,7 @@ struct SubscriptionFlowView: View { } } + private func setUpAppearances() { let navAppearance = UINavigationBar.appearance() diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 1d654701aa..82825e2a74 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -235,10 +235,19 @@ struct SubscriptionRestoreView: View { dismiss() }), secondaryButton: .cancel()) + + case .expired: + return Alert(title: Text(UserText.subscriptionRestoreNotFoundTitle), + message: Text(UserText.subscriptionRestoreNotFoundMessage), + primaryButton: .default(Text(UserText.subscriptionRestoreNotFoundPlans), + action: { + dismiss() + }), + secondaryButton: .cancel()) case .error: - return Alert(title: Text("Error"), message: Text("An error occurred during activation.")) + return Alert(title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage)) default: - return Alert(title: Text("Unknown"), message: Text("An unknown error occurred.")) + return Alert(title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage)) } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 6bffcd0195..2a304470cc 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1072,12 +1072,19 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionFoundCancel = NSLocalizedString("subscription.found.cancel", value: "Cancel", comment: "Cancel action for the existing subscription dialog") public static let subscriptionFoundRestore = NSLocalizedString("subscription.found.restore", value: "Restore", comment: "Restore action for the existing subscription dialog") public static let subscriptionRestoreNotFoundTitle = NSLocalizedString("subscription.notFound.alert.title", value: "Subscription Not Found", comment: "Alert title for not found subscription") - public static let subscriptionRestoreNotFoundMessage = NSLocalizedString("subscription.notFound.alert.message", value: "The subscription associated with this Apple ID is no longer active.", comment: "Alert content for not found subscription") + public static let subscriptionRestoreNotFoundMessage = NSLocalizedString("subscription.notFound.alert.message", value: "There is no subscription associated with this Apple ID.", comment: "Alert content for not found subscription") + public static let subscriptionRestoreExpiredFoundMessage = NSLocalizedString("subscription.expired.alert.message", value: "The subscription associated with this Apple ID is no longer active.", comment: "Alert content for not found subscription") public static let subscriptionRestoreNotFoundPlans = NSLocalizedString("subscription.notFound.view.plans", value: "View Plans", comment: "View plans button text") public static let subscriptionRestoreSuccessfulTitle = NSLocalizedString("subscription.restore.success.alert.title", value: "You’re all set.", comment: "Alert title for restored purchase") public static let subscriptionRestoreSuccessfulMessage = NSLocalizedString("subscription.restore.success.alert.message", value: "Your purchases have been restored.", comment: "Alert message for restored purchase") public static let subscriptionRestoreSuccessfulButton = NSLocalizedString("subscription.restore.success.alert.button", value: "OK", comment: "Alert button text for restored purchase alert") + public static let subscriptionRestoreEmailInactiveTitle = NSLocalizedString("subscription.email.inactive.alert.title", value: "Subscription Not Found", comment: "Alert title for not found subscription") + public static let subscriptionRestoreEmailInactiveMessage = NSLocalizedString("subscription.email.inactive.alert.message", value: "The subscription associated with this email is no longer active.", comment: "Alert content for not found subscription") + + public static let subscriptionAppStoreErrorTitle = NSLocalizedString("subscription.restore.general.error.title", value: "Something Went Wrong", comment: "Alert for general error title") + public static let subscriptionAppStoreErrorMessage = NSLocalizedString("subscription.restore.general.error.message", value: "The App Store was unable to process your purchase. Please try again later.", comment: "Alert for general error message") + // PIR: public static let subscriptionPIRHeroText = NSLocalizedString("subscription.pir.hero", value: "Activate Privacy Pro on desktop to set up Personal Information Removal", comment: "Hero Text for Personal information removal") public static let subscriptionPIRHeroDetail = NSLocalizedString("subscription.pir.heroText", value: "In the DuckDuckGo browser for desktop, go to %@ and click %@ to get started.", comment: "Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. ") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index c9ed159091..73101214b3 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1998,6 +1998,15 @@ But if you *do* want a peek under the hood, you can find more information about /* Title for Confirm messages */ "subscription.confirm.title" = "Are you sure?"; +/* Alert content for not found subscription */ +"subscription.email.inactive.alert.message" = "The subscription associated with this email is no longer active."; + +/* Alert title for not found subscription */ +"subscription.email.inactive.alert.title" = "Subscription Not Found"; + +/* Alert content for not found subscription */ +"subscription.expired.alert.message" = "The subscription associated with this Apple ID is no longer active."; + /* text for expiration string */ "subscription.expires" = "expires"; @@ -2038,7 +2047,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.monthly" = "Monthly Subscription"; /* Alert content for not found subscription */ -"subscription.notFound.alert.message" = "The subscription associated with this Apple ID is no longer active."; +"subscription.notFound.alert.message" = "There is no subscription associated with this Apple ID."; /* Alert title for not found subscription */ "subscription.notFound.alert.title" = "Subscription Not Found"; @@ -2091,6 +2100,12 @@ But if you *do* want a peek under the hood, you can find more information about /* text for renewal string */ "subscription.renews" = "renews"; +/* Alert for general error message */ +"subscription.restore.general.error.message" = "The App Store was unable to process your purchase. Please try again later."; + +/* Alert for general error title */ +"subscription.restore.general.error.title" = "Something Went Wrong"; + /* Alert button text for restored purchase alert */ "subscription.restore.success.alert.button" = "OK"; From 19283cc18f36890389ff7610cf0d696fd920c2d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Thu, 29 Feb 2024 22:54:50 +0100 Subject: [PATCH 065/245] Release 7.110.0-2 (#2522) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8f41b35304..a9b885e529 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8161,7 +8161,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8198,7 +8198,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8290,7 +8290,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8318,7 +8318,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8468,7 +8468,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8494,7 +8494,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8559,7 +8559,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8594,7 +8594,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8628,7 +8628,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8659,7 +8659,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8946,7 +8946,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8977,7 +8977,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9006,7 +9006,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9040,7 +9040,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9071,7 +9071,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9104,11 +9104,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9346,7 +9346,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9373,7 +9373,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9406,7 +9406,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9444,7 +9444,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9480,7 +9480,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9515,11 +9515,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9693,11 +9693,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9726,10 +9726,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From a4cd443cde1d062639b7923b3ec6dd112eff26ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 1 Mar 2024 00:59:42 +0100 Subject: [PATCH 066/245] Add simple behavior monitoring to better address potential breakage issues (#2521) --- Core/PixelEvent.swift | 17 ++ Core/UserDefaultsPropertyWrapper.swift | 4 + DuckDuckGo.xcodeproj/project.pbxproj | 28 +++ DuckDuckGo/AppDelegate.swift | 2 + DuckDuckGo/AppDependencyProvider.swift | 7 + DuckDuckGo/MainViewController.swift | 3 +- .../PrivacyDashboardViewController.swift | 1 + DuckDuckGo/TabViewController.swift | 3 + ...bViewControllerBrowsingMenuExtension.swift | 3 + DuckDuckGo/UserBehaviorEvent.swift | 32 ++++ DuckDuckGo/UserBehaviorMonitor.swift | 115 +++++++++++ DuckDuckGo/en.lproj/Localizable.strings | 2 +- DuckDuckGoTests/MockDependencyProvider.swift | 2 + .../UserBehaviorMonitorTests.swift | 181 ++++++++++++++++++ 14 files changed, 398 insertions(+), 2 deletions(-) create mode 100644 DuckDuckGo/UserBehaviorEvent.swift create mode 100644 DuckDuckGo/UserBehaviorMonitor.swift create mode 100644 DuckDuckGoTests/UserBehaviorMonitorTests.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index b1766ba89c..291db3d335 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -526,6 +526,14 @@ extension Pixel { case compilationFailed case appRatingPromptFetchError + + case userBehaviorReloadTwice + case userBehaviorReloadAndRestart + case userBehaviorReloadAndFireButton + case userBehaviorReloadAndOpenSettings + case userBehaviorReloadAndTogglePrivacyControls + case userBehaviorFireButtonAndRestart + case userBehaviorFireButtonAndTogglePrivacyControls } } @@ -1024,6 +1032,15 @@ extension Pixel.Event { case .debugReturnUserUpdateATB: return "m_debug_return_user_update_atb" case .appRatingPromptFetchError: return "m_d_app_rating_prompt_fetch_error" + + // MARK: - User behavior + case .userBehaviorReloadTwice: return "m_reload-twice" + case .userBehaviorReloadAndRestart: return "m_reload-and-restart" + case .userBehaviorReloadAndFireButton: return "m_reload-and-fire-button" + case .userBehaviorReloadAndOpenSettings: return "m_reload-and-open-settings" + case .userBehaviorReloadAndTogglePrivacyControls: return "m_reload-and-toggle-privacy-controls" + case .userBehaviorFireButtonAndRestart: return "m_fire-button-and-restart" + case .userBehaviorFireButtonAndTogglePrivacyControls: return "m_fire-button-and-toggle-privacy-controls" } } diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 7d4a96f316..9ab2131af7 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -123,6 +123,10 @@ public struct UserDefaultsWrapper { case privacyConfigCustomURL = "com.duckduckgo.ios.privacyConfigCustomURL" case subscriptionIsActive = "com.duckduckgo.ios.subscruption.isActive" + + case didRefreshTimestamp = "com.duckduckgo.ios.userBehavior.didRefreshTimestamp" + case didBurnTimestamp = "com.duckduckgo.ios.userBehavior.didBurnTimestamp" + } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a9b885e529..1ccc5e2138 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -749,6 +749,9 @@ CB2A7EEF283D185100885F67 /* RulesCompilationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EEE283D185100885F67 /* RulesCompilationMonitor.swift */; }; CB2A7EF128410DF700885F67 /* PixelEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF028410DF700885F67 /* PixelEvent.swift */; }; CB2A7EF4285383B300885F67 /* AppLastCompiledRulesStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */; }; + CB48D3322B90CE9F00631D8B /* UserBehaviorEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */; }; + CB48D3332B90CE9F00631D8B /* UserBehaviorMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */; }; + CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */; }; CB5516D0286500290079B175 /* TrackerRadarIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85519124247468580010FDD0 /* TrackerRadarIntegrationTests.swift */; }; CB5516D1286500290079B175 /* ContentBlockingRulesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02CA904C24FD2DB000D41DDF /* ContentBlockingRulesTests.swift */; }; CB5516D2286500290079B175 /* AtbServerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85F21DBD21121147002631A6 /* AtbServerTests.swift */; }; @@ -2393,6 +2396,9 @@ CB2A7EF3285383B300885F67 /* AppLastCompiledRulesStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLastCompiledRulesStore.swift; sourceTree = ""; }; CB2C47822AF6D55800AEDCD9 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/InfoPlist.strings; sourceTree = ""; }; CB4448752AF6D51D001F93F7 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = ""; }; + CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserBehaviorEvent.swift; sourceTree = ""; }; + CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitor.swift; sourceTree = ""; }; + CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitorTests.swift; sourceTree = ""; }; CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; CB6ABD002AF6D52B004A8224 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; CB6CE65B2AF6D4EE00119848 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -3721,6 +3727,7 @@ 84E341941E2F7EFB00BDBA6F /* DuckDuckGo */ = { isa = PBXGroup; children = ( + CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */, EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */, CB258D1129A4F1BB00DEBA24 /* Configuration */, 1E908BED29827C480008C8F3 /* Autoconsent */, @@ -4486,6 +4493,23 @@ path = Configuration; sourceTree = ""; }; + CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */ = { + isa = PBXGroup; + children = ( + CB48D3302B90CE9F00631D8B /* UserBehaviorEvent.swift */, + CB48D3312B90CE9F00631D8B /* UserBehaviorMonitor.swift */, + ); + name = UserBehaviorMonitor; + sourceTree = ""; + }; + CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */ = { + isa = PBXGroup; + children = ( + CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */, + ); + name = UserBehaviorMonitor; + sourceTree = ""; + }; CBAA195627BFDD9800A4BD49 /* SmarterEncryption */ = { isa = PBXGroup; children = ( @@ -4912,6 +4936,7 @@ F12D98401F266B30003C2EE3 /* DuckDuckGo */ = { isa = PBXGroup; children = ( + CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */, F17669A21E411D63003D3222 /* Application */, 026F08B629B7DC130079B9DF /* AppTrackingProtection */, 981FED7222045FFA008488D7 /* AutoClear */, @@ -6594,6 +6619,7 @@ F4147354283BF834004AA7A5 /* AutofillContentScopeFeatureToggles.swift in Sources */, 986DA94A24884B18004A7E39 /* WebViewTransition.swift in Sources */, 31B524572715BB23002225AB /* WebJSAlert.swift in Sources */, + CB48D3322B90CE9F00631D8B /* UserBehaviorEvent.swift in Sources */, 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */, F114C55B1E66EB020018F95F /* NibLoading.swift in Sources */, D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */, @@ -6775,6 +6801,7 @@ 9821234E2B6D0A6300F08C57 /* UserAuthenticator.swift in Sources */, 310C4B47281B60E300BA79A9 /* AutofillLoginDetailsViewModel.swift in Sources */, 85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */, + CB48D3332B90CE9F00631D8B /* UserBehaviorMonitor.swift in Sources */, 1EC458462948932500CB2B13 /* UIHostingControllerExtension.swift in Sources */, 1E4DCF4E27B6A69600961E25 /* DownloadsListHostingController.swift in Sources */, 850F93DB2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift in Sources */, @@ -6988,6 +7015,7 @@ B6AD9E3A28D456820019CDE9 /* PrivacyConfigurationManagerMock.swift in Sources */, F189AED71F18F6DE001EBAE1 /* TabTests.swift in Sources */, F13B4BFB1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift in Sources */, + CB48D3372B90DF2000631D8B /* UserBehaviorMonitorTests.swift in Sources */, 8528AE7E212EF5FF00D0BD74 /* AppRatingPromptTests.swift in Sources */, 981FED692201FE69008488D7 /* AutoClearSettingsScreenTests.swift in Sources */, 4BC21A2F27238B7500229F0E /* RunLoopExtensionTests.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 01bcadace1..82d6381202 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -329,6 +329,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { clearDebugWaitlistState() + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp) + return true } diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index b23caebde0..55ae87a87b 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -24,6 +24,7 @@ import DDGSync import Bookmarks protocol DependencyProvider { + var appSettings: AppSettings { get } var variantManager: VariantManager { get } var internalUserDecider: InternalUserDecider { get } @@ -36,11 +37,14 @@ protocol DependencyProvider { var autofillLoginSession: AutofillLoginSession { get } var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager { get } var configurationManager: ConfigurationManager { get } + var userBehaviorMonitor: UserBehaviorMonitor { get } + } /// Provides dependencies for objects that are not directly instantiated /// through `init` call (e.g. ViewControllers created from Storyboards). class AppDependencyProvider: DependencyProvider { + static var shared: DependencyProvider = AppDependencyProvider() let appSettings: AppSettings = AppUserDefaults() @@ -60,4 +64,7 @@ class AppDependencyProvider: DependencyProvider { lazy var autofillNeverPromptWebsitesManager = AutofillNeverPromptWebsitesManager() let configurationManager = ConfigurationManager() + + let userBehaviorMonitor = UserBehaviorMonitor() + } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 0860b07266..c2f67cd89d 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -2225,7 +2225,8 @@ extension MainViewController: AutoClearWorker { func forgetAllWithAnimation(transitionCompletion: (() -> Void)? = nil, showNextDaxDialog: Bool = false) { let spid = Instruments.shared.startTimedEvent(.clearingData) Pixel.fire(pixel: .forgetAllExecuted) - + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.burn) + tabManager.prepareAllTabsExceptCurrentForDataClearing() fireButtonAnimator.animate { diff --git a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift index 68521a29dd..98283d584a 100644 --- a/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/PrivacyDashboardViewController.swift @@ -113,6 +113,7 @@ class PrivacyDashboardViewController: UIViewController { } contentBlockingManager.scheduleCompilation() + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.toggleProtections) } private func privacyDashboardCloseHandler() { diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index b0c7df98ea..da4fbb14f2 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -421,6 +421,7 @@ class TabViewController: UIViewController { guard let self else { return } self.reload() Pixel.fire(pixel: .pullToRefresh) + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.refresh) }, for: .valueChanged) webView.scrollView.refreshControl?.backgroundColor = .systemBackground @@ -2090,6 +2091,8 @@ extension TabViewController: UIGestureRecognizerDelegate { } else { reload() } + + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.refresh) } } diff --git a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift index b946236540..2d4f1397c3 100644 --- a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift @@ -403,6 +403,7 @@ extension TabViewController { private func onBrowsingSettingsAction() { Pixel.fire(pixel: .browsingMenuSettings) + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.openSettings) delegate?.tabDidRequestSettings(tab: self) } @@ -441,5 +442,7 @@ extension TabViewController { onAction: { [weak self] in self?.togglePrivacyProtection(domain: domain) }) + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.toggleProtections) } + } diff --git a/DuckDuckGo/UserBehaviorEvent.swift b/DuckDuckGo/UserBehaviorEvent.swift new file mode 100644 index 0000000000..1de6af8a92 --- /dev/null +++ b/DuckDuckGo/UserBehaviorEvent.swift @@ -0,0 +1,32 @@ +// +// UserBehaviorEvent.swift +// DuckDuckGo +// +// 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 + +public enum UserBehaviorEvent { + + case reloadTwice + case reloadAndRestart + case reloadAndFireButton + case reloadAndOpenSettings + case reloadAndTogglePrivacyControls + case fireButtonAndRestart + case fireButtonAndTogglePrivacyControls + +} diff --git a/DuckDuckGo/UserBehaviorMonitor.swift b/DuckDuckGo/UserBehaviorMonitor.swift new file mode 100644 index 0000000000..076c665c3c --- /dev/null +++ b/DuckDuckGo/UserBehaviorMonitor.swift @@ -0,0 +1,115 @@ +// +// UserBehaviorMonitor.swift +// DuckDuckGo +// +// 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 Common +import Core + +protocol UserBehaviorStoring { + + var didRefreshTimestamp: Date? { get set } + var didBurnTimestamp: Date? { get set } + +} + +final class UserBehaviorStore: UserBehaviorStoring { + + @UserDefaultsWrapper(key: .didRefreshTimestamp, defaultValue: .distantPast) + var didRefreshTimestamp: Date? + + @UserDefaultsWrapper(key: .didBurnTimestamp, defaultValue: .distantPast) + var didBurnTimestamp: Date? + +} + +final class UserBehaviorMonitor { + + enum Action: Equatable { + + case refresh + case burn + case reopenApp + case openSettings + case toggleProtections + + } + + private let eventMapping: EventMapping + private var store: UserBehaviorStoring + + init(eventMapping: EventMapping = AppUserBehaviorMonitor.eventMapping, + store: UserBehaviorStoring = UserBehaviorStore()) { + self.eventMapping = eventMapping + self.store = store + } + + var didRefreshTimestamp: Date? { + get { store.didRefreshTimestamp } + set { store.didRefreshTimestamp = newValue } + } + + var didBurnTimestamp: Date? { + get { store.didBurnTimestamp } + set { store.didBurnTimestamp = newValue } + } + + func handleAction(_ action: Action, date: Date = Date()) { + switch action { + case .refresh: + fireEventIfActionOccurredRecently(since: didRefreshTimestamp, eventToFire: .reloadTwice, within: 10.0) + didRefreshTimestamp = date + case .burn: + fireEventIfActionOccurredRecently(since: didRefreshTimestamp, eventToFire: .reloadAndFireButton) + didBurnTimestamp = date + case .reopenApp: + fireEventIfActionOccurredRecently(since: didRefreshTimestamp, eventToFire: .reloadAndRestart) + fireEventIfActionOccurredRecently(since: didBurnTimestamp, eventToFire: .fireButtonAndRestart) + case .openSettings: + fireEventIfActionOccurredRecently(since: didRefreshTimestamp, eventToFire: .reloadAndOpenSettings) + case .toggleProtections: + fireEventIfActionOccurredRecently(since: didRefreshTimestamp, eventToFire: .reloadAndTogglePrivacyControls) + fireEventIfActionOccurredRecently(since: didBurnTimestamp, eventToFire: .fireButtonAndTogglePrivacyControls) + } + + func fireEventIfActionOccurredRecently(since timestamp: Date?, eventToFire: UserBehaviorEvent, within interval: Double = 30.0) { + if let timestamp = timestamp, date.timeIntervalSince(timestamp) < interval { + eventMapping.fire(eventToFire) + } + } + } + +} + +final class AppUserBehaviorMonitor { + + static let eventMapping = EventMapping { event, _, _, _ in + let domainEvent: Pixel.Event + switch event { + case .reloadTwice: domainEvent = .userBehaviorReloadTwice + case .reloadAndRestart: domainEvent = .userBehaviorReloadAndRestart + case .reloadAndFireButton: domainEvent = .userBehaviorReloadAndFireButton + case .reloadAndOpenSettings: domainEvent = .userBehaviorReloadAndOpenSettings + case .reloadAndTogglePrivacyControls: domainEvent = .userBehaviorReloadAndTogglePrivacyControls + case .fireButtonAndRestart: domainEvent = .userBehaviorFireButtonAndRestart + case .fireButtonAndTogglePrivacyControls: domainEvent = .userBehaviorFireButtonAndTogglePrivacyControls + } + Pixel.fire(pixel: domainEvent) + } + +} diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 2445871097..6217982ed6 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2005,7 +2005,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.manage.devices" = "Manage Devices"; /* Description for Email Management options */ -"subscription.manage.email.description" = "You can use this email to activate your subscription from browser settings in the DuckDuckGo app on your other devices."; +"subscription.manage.email.description" = "You can use this email to activate your subscription on your other devices."; /* Manage Plan header */ "subscription.manage.plan" = "Manage Plan"; diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift index 7676843d3c..0b53f456a4 100644 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ b/DuckDuckGoTests/MockDependencyProvider.swift @@ -36,6 +36,7 @@ class MockDependencyProvider: DependencyProvider { var autofillLoginSession: AutofillLoginSession var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager var configurationManager: ConfigurationManager + var userBehaviorMonitor: UserBehaviorMonitor init() { let defaultProvider = AppDependencyProvider() @@ -51,5 +52,6 @@ class MockDependencyProvider: DependencyProvider { autofillLoginSession = defaultProvider.autofillLoginSession autofillNeverPromptWebsitesManager = defaultProvider.autofillNeverPromptWebsitesManager configurationManager = defaultProvider.configurationManager + userBehaviorMonitor = defaultProvider.userBehaviorMonitor } } diff --git a/DuckDuckGoTests/UserBehaviorMonitorTests.swift b/DuckDuckGoTests/UserBehaviorMonitorTests.swift new file mode 100644 index 0000000000..d95cf04c17 --- /dev/null +++ b/DuckDuckGoTests/UserBehaviorMonitorTests.swift @@ -0,0 +1,181 @@ +// +// UserBehaviorMonitorTests.swift +// DuckDuckGo +// +// 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 XCTest +import Common +@testable import DuckDuckGo + +final class MockUserBehaviorEventsMapping: EventMapping { + + init(captureEvent: @escaping (UserBehaviorEvent) -> Void) { + super.init { event, _, _, _ in + captureEvent(event) + } + } + + override init(mapping: @escaping EventMapping.Mapping) { + fatalError("Use init()") + } +} + +final class MockUserBehaviorStore: UserBehaviorStoring { + + var didRefreshTimestamp: Date? + var didBurnTimestamp: Date? + +} + +final class UserBehaviorMonitorTests: XCTestCase { + + var eventMapping: MockUserBehaviorEventsMapping! + var monitor: UserBehaviorMonitor! + var events: [UserBehaviorEvent] = [] + + override func setUp() { + super.setUp() + events.removeAll() + eventMapping = MockUserBehaviorEventsMapping(captureEvent: { event in + self.events.append(event) + }) + monitor = UserBehaviorMonitor(eventMapping: eventMapping, + store: MockUserBehaviorStore()) + } + + // - MARK: Behavior testing + // Expecting events + + func testWhenUserRefreshesTwiceItSendsReloadTwiceEvent() { + monitor.handleAction(.refresh) + monitor.handleAction(.refresh) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadTwice) + } + + func testWhenUserRefreshesAndThenReopensAppItSendsReloadAndRestartEvent() { + monitor.handleAction(.refresh) + monitor.handleAction(.reopenApp) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadAndRestart) + } + + func testWhenUserRefreshesAndThenUsesFireButtonItSendsReloadAndFireButtonEvent() { + monitor.handleAction(.refresh) + monitor.handleAction(.burn) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadAndFireButton) + } + + func testWhenUserRefreshesAndThenOpensSettingsItSendsReloadAndOpenSettingsEvent() { + monitor.handleAction(.refresh) + monitor.handleAction(.openSettings) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadAndOpenSettings) + } + + func testWhenUserRefreshesAndThenTogglesProtectionsItSendsReloadAndTogglePrivacyControlsEvent() { + monitor.handleAction(.refresh) + monitor.handleAction(.toggleProtections) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadAndTogglePrivacyControls) + } + + func testWhenUserUsesFireButtonAndThenReopensAppItSendsFireButtonAndRestartEvent() { + monitor.handleAction(.burn) + monitor.handleAction(.reopenApp) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .fireButtonAndRestart) + } + + func testWhenUserUsesFireButtonAndThenTogglesProtectionsItSendsFireButtonAndTogglePrivacyControlsEvent() { + monitor.handleAction(.burn) + monitor.handleAction(.toggleProtections) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .fireButtonAndTogglePrivacyControls) + } + + func testWhenUserUsesFireButtonThenOpensSettingsThenReopensAppItSendsFireButtonAndRestartEvent() { + monitor.handleAction(.burn) + monitor.handleAction(.openSettings) + monitor.handleAction(.reopenApp) + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .fireButtonAndRestart) + } + + func testWhenUserUsesFireButtonThenRefreshesThenReopensAppItSendsTwoEvents() { + monitor.handleAction(.burn) + monitor.handleAction(.refresh) + monitor.handleAction(.reopenApp) + XCTAssertEqual(events.count, 2) + XCTAssertEqual(events[0], .reloadAndRestart) + XCTAssertEqual(events[1], .fireButtonAndRestart) + } + + func testWhenUserRefreshesThenReopensAppThenUsesFireButtonThenItSendsThreeEvents() { + monitor.handleAction(.refresh) + monitor.handleAction(.burn) + monitor.handleAction(.reopenApp) + XCTAssertEqual(events.count, 3) + XCTAssertEqual(events[0], .reloadAndFireButton) + XCTAssertEqual(events[1], .reloadAndRestart) + XCTAssertEqual(events[2], .fireButtonAndRestart) + } + + // Not expecting any events + + func testWhenUserUsesFireButtonAndThenRefreshesItShouldNotSendAnyEvent() { + monitor.handleAction(.burn) + monitor.handleAction(.refresh) + XCTAssertTrue(events.isEmpty) + } + + // Timing + + func testFireReloadTwiceEventOnlyIfItHappenedWithin10seconds() { + let date = Date() + monitor.handleAction(.refresh, date: date) + monitor.handleAction(.refresh, date: date + 10) // 10 seconds after the first event + XCTAssertTrue(events.isEmpty) + monitor.handleAction(.refresh, date: date + 15) // 5 seconds after the second event + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadTwice) + } + + func testFireReloadAndRestartEventOnlyIfItHappenedWithin30seconds() { + let date = Date() + monitor.handleAction(.refresh, date: date) + monitor.handleAction(.reopenApp, date: date + 30) // 30 seconds after the first event + XCTAssertTrue(events.isEmpty) + monitor.handleAction(.refresh, date: date + 30) + monitor.handleAction(.reopenApp, date: date + 50) // 20 seconds after the second event + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .reloadAndRestart) + } + + func testFireButtonAndRestartEventOnlyIfItHappenedWithin30seconds() { + let date = Date() + monitor.handleAction(.burn, date: date) + monitor.handleAction(.reopenApp, date: date + 30) // 30 seconds after the first event + XCTAssertTrue(events.isEmpty) + monitor.handleAction(.burn, date: date + 30) + monitor.handleAction(.reopenApp, date: date + 50) // 20 seconds after the second event + XCTAssertEqual(events.count, 1) + XCTAssertEqual(events.first!, .fireButtonAndRestart) + } + +} From e2e667e411d4e27269cb0d7a1d3e7911be52bafe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Fri, 1 Mar 2024 01:05:47 +0100 Subject: [PATCH 067/245] Release 7.110.0-3 (#2523) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1ccc5e2138..c2ed546042 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8189,7 +8189,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8226,7 +8226,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8318,7 +8318,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8346,7 +8346,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8496,7 +8496,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8522,7 +8522,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8587,7 +8587,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8622,7 +8622,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8656,7 +8656,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8687,7 +8687,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8974,7 +8974,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9005,7 +9005,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9034,7 +9034,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9068,7 +9068,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9099,7 +9099,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9132,11 +9132,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9374,7 +9374,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9401,7 +9401,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9434,7 +9434,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9472,7 +9472,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9508,7 +9508,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9543,11 +9543,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9721,11 +9721,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9754,10 +9754,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 5f06e4c17f76547412045190602b19a3ed2fd4f7 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 1 Mar 2024 00:58:27 +0000 Subject: [PATCH 068/245] update to use bsk with history (#2514) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ad6a46fc88..205c844c33 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9949,7 +9949,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 113.1.0; + version = 114.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f69b61dde0..cf556941ff 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "7f5c89edfdf38cec173f125f46bacad43960d2d4", - "version" : "113.1.0" + "revision" : "c9eae1b4a4ce5b6854d6934e57f900f62d2f3917", + "version" : "114.0.0" } }, { From 11ec9e4e48ed359cba50127c4bee33a4dd6196aa Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Fri, 1 Mar 2024 00:54:02 -0500 Subject: [PATCH 069/245] Check entitlement periodically and while rekeying NetP (#2461) Task/Issue URL: https://app.asana.com/0/0/1206409081785856/f Description: This PR adds entitlement check for NetP --- ...workProtectionNotificationIdentifier.swift | 1 + DuckDuckGo.xcodeproj/project.pbxproj | 8 ++--- .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/AppDelegate.swift | 13 ++++++++ DuckDuckGo/CriticalAlerts.swift | 16 ++++++++++ .../EventMapping+NetworkProtectionError.swift | 2 ++ DuckDuckGo/MainViewController.swift | 30 ++++++------------- ...orkProtectionConvenienceInitialisers.swift | 9 ++++-- DuckDuckGo/UserText.swift | 9 ++++-- DuckDuckGo/en.lproj/Localizable.strings | 14 ++++++++- ...etworkProtectionPacketTunnelProvider.swift | 28 ++++++++++++++--- ...orkProtectionUNNotificationPresenter.swift | 16 +++++++++- PacketTunnelProvider/UserText.swift | 2 ++ .../en.lproj/Localizable.strings | 3 ++ 14 files changed, 117 insertions(+), 38 deletions(-) diff --git a/Core/NetworkProtectionNotificationIdentifier.swift b/Core/NetworkProtectionNotificationIdentifier.swift index 20e1a7631a..5ac235621c 100644 --- a/Core/NetworkProtectionNotificationIdentifier.swift +++ b/Core/NetworkProtectionNotificationIdentifier.swift @@ -23,4 +23,5 @@ public enum NetworkProtectionNotificationIdentifier: String { case connection = "network-protection.notification.connection" case superseded = "network-protection.notification.superseded" case test = "network-protection.notification.test" + case entitlement = "network-protection.notification.entitlement" } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 205c844c33..3de5ac902f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8944,7 +8944,7 @@ MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG APP_TRACKING_PROTECTION NETWORK_PROTECTION SUBSCRIPTION ALPHA"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; @@ -8956,7 +8956,7 @@ buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = "DDG-AppIcon-Alpha"; - CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; + CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CURRENT_PROJECT_VERSION = 3; @@ -9081,7 +9081,7 @@ CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; + CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 3; @@ -9949,7 +9949,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.0.0; + version = 114.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cf556941ff..018b6e092f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "c9eae1b4a4ce5b6854d6934e57f900f62d2f3917", - "version" : "114.0.0" + "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", + "version" : "114.1.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 2745fc9dc2..1f712dd2c6 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -301,6 +301,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() #endif +#if NETWORK_PROTECTION && SUBSCRIPTION + if VPNSettings(defaults: .networkProtectionGroupDefaults).showEntitlementAlert { + presentExpiredEntitlementAlert() + } +#endif + RemoteMessaging.registerBackgroundRefreshTaskHandler( bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: AppDependencyProvider.shared.appSettings.favoritesDisplayMode @@ -346,6 +352,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window?.rootViewController?.present(alertController, animated: true, completion: nil) } + private func presentExpiredEntitlementAlert() { + let alertController = CriticalAlerts.makeExpiredEntitlementAlert() + window?.rootViewController?.present(alertController, animated: true) { + VPNSettings(defaults: .networkProtectionGroupDefaults).apply(change: .setShowEntitlementAlert(false)) + } + } + private func cleanUpMacPromoExperiment2() { UserDefaults.standard.removeObject(forKey: "com.duckduckgo.ios.macPromoMay23.exp2.cohort") } diff --git a/DuckDuckGo/CriticalAlerts.swift b/DuckDuckGo/CriticalAlerts.swift index c2e46242b3..c62183fe0c 100644 --- a/DuckDuckGo/CriticalAlerts.swift +++ b/DuckDuckGo/CriticalAlerts.swift @@ -70,4 +70,20 @@ struct CriticalAlerts { return alertController } + static func makeExpiredEntitlementAlert() -> UIAlertController { + let alertController = UIAlertController(title: UserText.vpnAccessRevokedAlertTitle, + message: UserText.vpnAccessRevokedAlertMessage, + preferredStyle: .alert) + alertController.overrideUserInterfaceStyle() + + let closeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionCancel, style: .cancel) + let signInButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in + UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) + } + + alertController.addAction(closeButton) + alertController.addAction(signInButton) + return alertController + } + } diff --git a/DuckDuckGo/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/EventMapping+NetworkProtectionError.swift index 2f465c4113..8aaa5c559a 100644 --- a/DuckDuckGo/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/EventMapping+NetworkProtectionError.swift @@ -70,6 +70,8 @@ extension EventMapping where Event == NetworkProtectionError { params[PixelParameters.keychainErrorCode] = String(status) case .noAuthTokenFound: pixelEvent = .networkProtectionNoAuthTokenFoundError + case .vpnAccessRevoked: + return case .noServerRegistrationInfo, .couldNotSelectClosestServer, diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index c2f67cd89d..d125eaccaf 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -263,7 +263,7 @@ class MainViewController: UIViewController { subscribeToEmailProtectionStatusNotifications() #if NETWORK_PROTECTION && SUBSCRIPTION - subscribeToNetworkProtectionSubscriptionEvents() + subscribeToNetworkProtectionEvents() #endif findInPageView.delegate = self @@ -1327,19 +1327,13 @@ class MainViewController: UIViewController { } #if NETWORK_PROTECTION && SUBSCRIPTION - private func subscribeToNetworkProtectionSubscriptionEvents() { + private func subscribeToNetworkProtectionEvents() { NotificationCenter.default.publisher(for: .accountDidSignIn) .receive(on: DispatchQueue.main) .sink { [weak self] notification in self?.onNetworkProtectionAccountSignIn(notification) } .store(in: &netpCancellables) - NotificationCenter.default.publisher(for: .accountDidSignOut) - .receive(on: DispatchQueue.main) - .sink { [weak self] notification in - self?.onNetworkProtectionAccountSignOut(notification) - } - .store(in: &netpCancellables) } @objc @@ -1349,25 +1343,19 @@ class MainViewController: UIViewController { return } + VPNSettings(defaults: .networkProtectionGroupDefaults).resetEntitlementMessaging() + print("[NetP Subscription] Reset expired entitlement messaging") + Task { do { - try await NetworkProtectionCodeRedemptionCoordinator().exchange(accessToken: token) - print("[NetP Subscription] Exchanged access token for auth token successfully") + // todo - https://app.asana.com/0/0/1206541966681608/f + try NetworkProtectionKeychainTokenStore().store(NetworkProtectionKeychainTokenStore.makeToken(from: token)) + print("[NetP Subscription] Stored derived NetP auth token") } catch { - print("[NetP Subscription] Failed to exchange access token for auth token: \(error)") + print("[NetP Subscription] Failed to store derived NetP auth token: \(error)") } } } - - @objc - private func onNetworkProtectionAccountSignOut(_ notification: Notification) { - do { - try NetworkProtectionKeychainTokenStore().deleteToken() - print("[NetP Subscription] Deleted NetP auth token after signing out from Privacy Pro") - } catch { - print("[NetP Subscription] Failed to delete NetP auth token after signing out from Privacy Pro: \(error)") - } - } #endif @objc diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 01d9287bd7..922220f2a8 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -58,7 +58,8 @@ extension NetworkProtectionKeychainTokenStore { convenience init() { self.init(keychainType: .dataProtection(.unspecified), serviceName: "\(Bundle.main.bundleIdentifier!).authToken", - errorEvents: .networkProtectionAppDebugEvents) + errorEvents: .networkProtectionAppDebugEvents, + isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription)) } } @@ -69,7 +70,8 @@ extension NetworkProtectionCodeRedemptionCoordinator { environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), isManualCodeRedemptionFlow: isManualCodeRedemptionFlow, - errorEvents: .networkProtectionAppDebugEvents + errorEvents: .networkProtectionAppDebugEvents, + isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription) ) } } @@ -95,7 +97,8 @@ extension NetworkProtectionLocationListCompositeRepository { self.init( environment: settings.selectedEnvironment, tokenStore: NetworkProtectionKeychainTokenStore(), - errorEvents: .networkProtectionAppDebugEvents + errorEvents: .networkProtectionAppDebugEvents, + isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription) ) } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 2a304470cc..a9e746b13a 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -658,6 +658,11 @@ In addition to the details entered into this form, your app issue report will co static let vpnFeedbackFormSubmittedMessage = NSLocalizedString("vpn.feedback-form.submitted.message", value: "Thank You! Feedback submitted.", comment: "Toast message when the VPN feedback form is submitted successfully") + static let vpnAccessRevokedAlertTitle = NSLocalizedString("vpn.access-revoked.alert.title", value: "VPN disconnected due to expired subscription", comment: "Alert title for the alert when the Privacy Pro subscription expires") + static let vpnAccessRevokedAlertMessage = NSLocalizedString("vpn.access-revoked.alert.message", value: "Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.", comment: "Alert message for the alert when the Privacy Pro subscription expiress") + static let vpnAccessRevokedAlertActionSubscribe = NSLocalizedString("vpn.access-revoked.alert.action.subscribe", value: "Subscribe", comment: "Primary action for the alert when the subscription expires") + static let vpnAccessRevokedAlertActionCancel = NSLocalizedString("vpn.access-revoked.alert.action.cancel", value: "Dismiss", comment: "Cancel action for the alert when the subscription expires") + // MARK: Notifications public static let macWaitlistAvailableNotificationTitle = NSLocalizedString("mac-waitlist.available.notification.title", value: "DuckDuckGo for Mac is ready!", comment: "Title for the macOS waitlist notification") @@ -919,10 +924,10 @@ But if you *do* want a peek under the hood, you can find more information about static let networkProtectionNotificationPromptTitle = NSLocalizedString("network-protection.waitlist.notification-prompt-title", value: "Know the instant you're invited", comment: "Title for the alert to confirm enabling notifications") static let networkProtectionNotificationPromptDescription = NSLocalizedString("network-protection.waitlist.notification-prompt-description", value: "Get a notification when your copy of Network Protection early access is ready.", comment: "Subtitle for the alert to confirm enabling notifications") - + // MARK: Settings Screeen public static let settingsTitle = NSLocalizedString("settings.title", value: "Settings", comment: "Title for the Settings View") - + // General Section public static let settingsSetDefault = NSLocalizedString("settings.default.browser", value: "Set as Default Browser", comment: "Settings screen cell text for setting the app as default browser") public static let settingsAddToDock = NSLocalizedString("settings.add.to.dock", value: "Add App to Your Dock", comment: "Settings screen cell text for adding the app to the dock") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 28af223ca9..9229afac4d 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2035,7 +2035,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.manage.devices" = "Manage Devices"; /* Description for Email Management options */ -"subscription.manage.email.description" = "You can use this email to activate your subscription on your other devices."; +"subscription.manage.email.description" = "You can use this email to activate your subscription from browser settings in the DuckDuckGo app on your other devices."; /* Manage Plan header */ "subscription.manage.plan" = "Manage Plan"; @@ -2238,6 +2238,18 @@ But if you *do* want a peek under the hood, you can find more information about /* Voice-search footer note with on-device privacy warning */ "voiceSearch.footer.note" = "Audio is processed on-device. It's not stored or shared with anyone, including DuckDuckGo."; +/* Cancel action for the alert when the subscription expires */ +"vpn.access-revoked.alert.action.cancel" = "Dismiss"; + +/* Primary action for the alert when the subscription expires */ +"vpn.access-revoked.alert.action.subscribe" = "Subscribe"; + +/* Alert message for the alert when the Privacy Pro subscription expiress */ +"vpn.access-revoked.alert.message" = "Subscribe to Privacy Pro to reconnect DuckDuckGo VPN."; + +/* Alert title for the alert when the Privacy Pro subscription expires */ +"vpn.access-revoked.alert.title" = "VPN disconnected due to expired subscription"; + /* Title for the Cancel button of the VPN feedback form */ "vpn.feedback-form.button.cancel" = "Cancel"; diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index dc5ddd2d44..f9e7fa92f7 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -26,6 +26,7 @@ import Core import Networking import NetworkExtension import NetworkProtection +import Subscription // Initial implementation for initial Network Protection tests. Will be fleshed out with https://app.asana.com/0/1203137811378537/1204630829332227/f final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { @@ -162,6 +163,8 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { params[PixelParameters.wireguardErrorCode] = String(code) case .noAuthTokenFound: pixelEvent = .networkProtectionNoAuthTokenFoundError + case .vpnAccessRevoked: + return case .unhandledError(function: let function, line: let line, error: let error): pixelEvent = .networkProtectionUnhandledError params[PixelParameters.function] = function @@ -201,24 +204,32 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } @objc init() { +#if ALPHA + let isSubscriptionEnabled = true +#else + let isSubscriptionEnabled = false +#endif let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), - errorEvents: nil) + errorEvents: nil, + isSubscriptionEnabled: isSubscriptionEnabled) let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) - let nofificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( + let notificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( settings: settings, wrappee: notificationsPresenter ) notificationsPresenter.requestAuthorization() - super.init(notificationsPresenter: nofificationsPresenterDecorator, + super.init(notificationsPresenter: notificationsPresenterDecorator, tunnelHealthStore: NetworkProtectionTunnelHealthStore(), controllerErrorStore: errorStore, keychainType: .dataProtection(.unspecified), tokenStore: tokenStore, debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents, - settings: settings) + settings: settings, + isSubscriptionEnabled: isSubscriptionEnabled, + entitlementCheck: Self.entitlementCheck) startMonitoringMemoryPressureEvents() observeServerChanges() APIRequest.Headers.setUserAgent(DefaultUserAgentManager.duckDuckGoUserAgent) @@ -265,6 +276,15 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { activationDateStore.updateLastActiveDate() } + private static func entitlementCheck() async -> Result { + let result = await AccountManager().hasEntitlement(for: .networkProtection) + switch result { + case .success(let hasEntitlement): + return .success(hasEntitlement) + case .failure(let error): + return .failure(error) + } + } } #endif diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift index 28358d72c8..0afc858191 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift @@ -62,7 +62,7 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio content.title = UserText.networkProtectionNotificationsTitle content.body = body - if #available(iOSApplicationExtension 15.0, *) { + if #available(iOS 15.0, *) { content.interruptionLevel = .timeSensitive content.relevanceScore = 0 } @@ -105,6 +105,20 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio func showSupersededNotification() { } + func showEntitlementNotification(completion: @escaping (Error?) -> Void) { + let identifier = NetworkProtectionNotificationIdentifier.entitlement.rawValue + let content = notificationContent(body: UserText.networkProtectionEntitlementExpiredNotificationBody) + let request = UNNotificationRequest(identifier: identifier, content: content, trigger: .none) + + requestAlertAuthorization { authorized in + guard authorized else { return } + self.userNotificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier]) + self.userNotificationCenter.add(request) { error in + completion(error) + } + } + } + private func showNotification(_ identifier: NetworkProtectionNotificationIdentifier, _ content: UNNotificationContent) { let request = UNNotificationRequest(identifier: identifier.rawValue, content: content, trigger: .none) diff --git a/PacketTunnelProvider/UserText.swift b/PacketTunnelProvider/UserText.swift index 820a4a6ebf..7243db5e2b 100644 --- a/PacketTunnelProvider/UserText.swift +++ b/PacketTunnelProvider/UserText.swift @@ -40,5 +40,7 @@ final class UserText { static let networkProtectionConnectionInterruptedNotificationBody = NSLocalizedString("network.protection.interrupted.notification.body", value: "Network Protection was interrupted. Attempting to reconnect now...", comment: "The body of the notification shown when Network Protection's connection is interrupted") static let networkProtectionConnectionFailureNotificationBody = NSLocalizedString("network.protection.failure.notification.body", value: "Network Protection failed to connect. Please try again later.", comment: "The body of the notification shown when Network Protection fails to reconnect") + + static let networkProtectionEntitlementExpiredNotificationBody = NSLocalizedString("network.protection.entitlement.expired.notification.body", value: "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.", comment: "The body of the notification when Privacy Pro subscription expired") } // swiftlint:enable line_length diff --git a/PacketTunnelProvider/en.lproj/Localizable.strings b/PacketTunnelProvider/en.lproj/Localizable.strings index 1a6d5fe4de..3ceec0f887 100644 --- a/PacketTunnelProvider/en.lproj/Localizable.strings +++ b/PacketTunnelProvider/en.lproj/Localizable.strings @@ -1,3 +1,6 @@ +/* The body of the notification when Privacy Pro subscription expired */ +"network.protection.entitlement.expired.notification.body" = "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN."; + /* The body of the notification shown when Network Protection fails to reconnect */ "network.protection.failure.notification.body" = "Network Protection failed to connect. Please try again later."; From 3fbcab64e658d8331d7c426e2638fb74fbf87845 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 1 Mar 2024 15:39:44 +0100 Subject: [PATCH 070/245] 15. Subscriptions: Properly sign out users and cache state (#2520) Task/Issue URL: https://app.asana.com/0/0/1206707680638927/f Description: If the subscription has become inactive, the user may still see the options in settings. This forces a signout in that case and caches the subscription state data properly. --- DuckDuckGo/SettingsViewModel.swift | 31 ++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 1623d2e0c8..2121cbc65e 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -340,15 +340,14 @@ extension SettingsViewModel { // Fetch available subscriptions from the backend (or sign out) switch await SubscriptionService.getSubscriptionDetails(token: token) { case .success(let response) where !response.isSubscriptionActive: - AccountManager().signOut() - setupSubscriptionPurchaseOptions() + + // Account is active but there's not a valid subscription + signOutUser() case .success(let response): // Cache Subscription state - self.state.subscription.hasActiveSubscription = true - Self.cachedHasActiveSubscription = self.state.subscription.hasActiveSubscription - + cacheSubscriptionState(active: true) // Check entitlements and update UI accordingly let entitlements: [AccountManager.Entitlement] = [.identityTheftRestoration, .dataBrokerProtection, .networkProtection] @@ -364,19 +363,27 @@ extension SettingsViewModel { } } } - - // Enable Subscription purchase if there's no active subscription - if !self.state.subscription.hasActiveSubscription { - setupSubscriptionPurchaseOptions() - } + default: - setupSubscriptionPurchaseOptions() + signOutUser() } } + @available(iOS 15.0, *) + private func signOutUser() { + AccountManager().signOut() + cacheSubscriptionState(active: false) + setupSubscriptionPurchaseOptions() + } + + private func cacheSubscriptionState(active: Bool) { + self.state.subscription.hasActiveSubscription = active + Self.cachedHasActiveSubscription = active + } + @available(iOS 15.0, *) private func setupSubscriptionPurchaseOptions() { - self.state.subscription.hasActiveSubscription = false + cacheSubscriptionState(active: false) PurchaseManager.shared.$availableProducts .receive(on: RunLoop.main) .sink { [weak self] products in From 62c54ab2d2c6a3d7f1bdbe53edf2cd638f256953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Fri, 1 Mar 2024 17:04:05 +0100 Subject: [PATCH 071/245] Report Apple Ad attribution using pixel (#2510) --- Core/Pixel.swift | 9 + Core/PixelEvent.swift | 5 + Core/UserDefaultsPropertyWrapper.swift | 3 +- DuckDuckGo.xcodeproj/project.pbxproj | 36 +++ .../AdAttribution/AdAttributionFetcher.swift | 159 ++++++++++++++ .../AdAttributionPixelReporter.swift | 96 ++++++++ .../AdAttributionReporterStorage.swift | 38 ++++ DuckDuckGo/AppDelegate.swift | 8 + .../AdAttributionFetcherTests.swift | 183 ++++++++++++++++ .../AdAttributionPixelReporterTests.swift | 206 ++++++++++++++++++ 10 files changed, 742 insertions(+), 1 deletion(-) create mode 100644 DuckDuckGo/AdAttribution/AdAttributionFetcher.swift create mode 100644 DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift create mode 100644 DuckDuckGo/AdAttribution/AdAttributionReporterStorage.swift create mode 100644 DuckDuckGoTests/AdAttributionFetcherTests.swift create mode 100644 DuckDuckGoTests/AdAttributionPixelReporterTests.swift diff --git a/Core/Pixel.swift b/Core/Pixel.swift index 155ced7fae..dd0a8e77a6 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -127,6 +127,15 @@ public struct PixelParameters { public static let returnUserErrorCode = "error_code" public static let returnUserOldATB = "old_atb" public static let returnUserNewATB = "new_atb" + + // Ad Attribution + public static let adAttributionOrgID = "org_id" + public static let adAttributionCampaignID = "campaign_id" + public static let adAttributionConversionType = "conversion_type" + public static let adAttributionAdGroupID = "ad_group_id" + public static let adAttributionCountryOrRegion = "country_or_region" + public static let adAttributionKeywordID = "keyword_id" + public static let adAttributionAdID = "ad_id" } public struct PixelValues { diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 291db3d335..527ce9c495 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -527,6 +527,8 @@ extension Pixel { case appRatingPromptFetchError + case appleAdAttribution + case userBehaviorReloadTwice case userBehaviorReloadAndRestart case userBehaviorReloadAndFireButton @@ -1032,6 +1034,9 @@ extension Pixel.Event { case .debugReturnUserUpdateATB: return "m_debug_return_user_update_atb" case .appRatingPromptFetchError: return "m_d_app_rating_prompt_fetch_error" + + // MARK: - Apple Ad Attribution + case .appleAdAttribution: return "m_apple-ad-attribution" // MARK: - User behavior case .userBehaviorReloadTwice: return "m_reload-twice" diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 9ab2131af7..f0515137b2 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -124,9 +124,10 @@ public struct UserDefaultsWrapper { case subscriptionIsActive = "com.duckduckgo.ios.subscruption.isActive" + case appleAdAttributionReportCompleted = "com.duckduckgo.ios.appleAdAttributionReport.completed" + case didRefreshTimestamp = "com.duckduckgo.ios.userBehavior.didRefreshTimestamp" case didBurnTimestamp = "com.duckduckgo.ios.userBehavior.didBurnTimestamp" - } private let key: Key diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3de5ac902f..c94784731f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -309,7 +309,12 @@ 4BFB911B29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */; }; 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; }; 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; + 6FD1BAE42B87A107000C475C /* AdAttributionPixelReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD1BAE12B87A107000C475C /* AdAttributionPixelReporter.swift */; }; + 6FD1BAE52B87A107000C475C /* AdAttributionReporterStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD1BAE22B87A107000C475C /* AdAttributionReporterStorage.swift */; }; + 6FD1BAE62B87A107000C475C /* AdAttributionFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD1BAE32B87A107000C475C /* AdAttributionFetcher.swift */; }; + 6FD3AEE32B8F4EEB0060FCCC /* AdAttributionPixelReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FD3AEE12B8DFBB80060FCCC /* AdAttributionPixelReporterTests.swift */; }; 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; + 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; 83004E882193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E872193E8C700DA013C /* TabViewControllerLongPressMenuExtension.swift */; }; @@ -1399,7 +1404,12 @@ 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimator.swift; sourceTree = ""; }; 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimatorTests.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; + 6FD1BAE12B87A107000C475C /* AdAttributionPixelReporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdAttributionPixelReporter.swift; path = AdAttribution/AdAttributionPixelReporter.swift; sourceTree = ""; }; + 6FD1BAE22B87A107000C475C /* AdAttributionReporterStorage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdAttributionReporterStorage.swift; path = AdAttribution/AdAttributionReporterStorage.swift; sourceTree = ""; }; + 6FD1BAE32B87A107000C475C /* AdAttributionFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AdAttributionFetcher.swift; path = AdAttribution/AdAttributionFetcher.swift; sourceTree = ""; }; + 6FD3AEE12B8DFBB80060FCCC /* AdAttributionPixelReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionPixelReporterTests.swift; sourceTree = ""; }; 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDisplayHelper.swift; sourceTree = ""; }; + 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIAlertControllerExtension.swift; path = ../Core/UIAlertControllerExtension.swift; sourceTree = ""; }; 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabViewControllerBrowsingMenuExtension.swift; sourceTree = ""; }; @@ -3545,6 +3555,25 @@ name = VPN; sourceTree = ""; }; + 6FD1BAE02B87A0E8000C475C /* AdAttribution */ = { + isa = PBXGroup; + children = ( + 6FD1BAE32B87A107000C475C /* AdAttributionFetcher.swift */, + 6FD1BAE12B87A107000C475C /* AdAttributionPixelReporter.swift */, + 6FD1BAE22B87A107000C475C /* AdAttributionReporterStorage.swift */, + ); + name = AdAttribution; + sourceTree = ""; + }; + 6FF9157F2B88E04F0042AC87 /* AdAttribution */ = { + isa = PBXGroup; + children = ( + 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */, + 6FD3AEE12B8DFBB80060FCCC /* AdAttributionPixelReporterTests.swift */, + ); + name = AdAttribution; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -3730,6 +3759,7 @@ CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */, EE3B98EA2A9634CC002F63A0 /* DuckDuckGoAlpha.entitlements */, CB258D1129A4F1BB00DEBA24 /* Configuration */, + 6FD1BAE02B87A0E8000C475C /* AdAttribution */, 1E908BED29827C480008C8F3 /* Autoconsent */, 3157B43627F4C8380042D3D7 /* Favicons */, AA4D6A8023DE4973007E8790 /* AppIcon */, @@ -4936,6 +4966,7 @@ F12D98401F266B30003C2EE3 /* DuckDuckGo */ = { isa = PBXGroup; children = ( + 6FF9157F2B88E04F0042AC87 /* AdAttribution */, CB48D3342B90CEBD00631D8B /* UserBehaviorMonitor */, F17669A21E411D63003D3222 /* Application */, 026F08B629B7DC130079B9DF /* AppTrackingProtection */, @@ -6485,6 +6516,7 @@ 319A371028299A850079FBCE /* PasswordHider.swift in Sources */, 982C87C42255559A00919035 /* UITableViewCellExtension.swift in Sources */, B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, + 6FD1BAE42B87A107000C475C /* AdAttributionPixelReporter.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, @@ -6661,6 +6693,7 @@ 31CB4251273AF50700FA0F3F /* SpeechRecognizerProtocol.swift in Sources */, 319A37172829C8AD0079FBCE /* UITableViewExtension.swift in Sources */, 85EE7F59224673C5000FE757 /* WebContainerNavigationController.swift in Sources */, + 6FD1BAE52B87A107000C475C /* AdAttributionReporterStorage.swift in Sources */, D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */, F4C9FBF528340DDA002281CC /* AutofillInterfaceEmailTruncator.swift in Sources */, 1E016AB42949FEB500F21625 /* OmniBarNotificationViewModel.swift in Sources */, @@ -6881,6 +6914,7 @@ 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, + 6FD1BAE62B87A107000C475C /* AdAttributionFetcher.swift in Sources */, EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */, EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, @@ -6959,6 +6993,7 @@ CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */, 986B45D0299E30A50089D2D7 /* BookmarkEntityTests.swift in Sources */, B6AD9E3828D4512E0019CDE9 /* EmbeddedTrackerDataTests.swift in Sources */, + 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */, 1E722729292EB24D003B5F53 /* AppSettingsMock.swift in Sources */, 8536A1C8209AF2410050739E /* MockVariantManager.swift in Sources */, C1B7B53428944EFA0098FD6A /* CoreDataTestUtilities.swift in Sources */, @@ -7035,6 +7070,7 @@ 9847C00527A41A0A00DB07AA /* WebViewTestHelper.swift in Sources */, 3170048227A9504F00C03F35 /* DownloadMocks.swift in Sources */, 317045C02858C6B90016ED1F /* AutofillInterfaceEmailTruncatorTests.swift in Sources */, + 6FD3AEE32B8F4EEB0060FCCC /* AdAttributionPixelReporterTests.swift in Sources */, 987130C6294AAB9F00AB05E0 /* BookmarkListViewModelTests.swift in Sources */, F1134ED21F40EF3A00B73467 /* JsonTestDataLoader.swift in Sources */, 4B83397129AC18C9003F7EA9 /* AppTrackingProtectionStoringModelTests.swift in Sources */, diff --git a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift new file mode 100644 index 0000000000..a9d3d31a80 --- /dev/null +++ b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift @@ -0,0 +1,159 @@ +// +// AdAttributionFetcher.swift +// DuckDuckGo +// +// 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 AdServices +import Common + +protocol AdAttributionFetcher { + func fetch() async -> AdServicesAttributionResponse? +} + +/// Fetches ad attribution data for from Apple. +/// +/// DuckDuckGo uses the AdServices framework to fetch and monitor anonymous install attribution data from Apple. No personally identifiable data is involved. +/// DuckDuckGo does not use the App Tracking Transparency framework at any point, and only uses the “standard” attribution payload. +/// See https://developer.apple.com/documentation/adservices/aaattribution/attributiontoken()#Attribution-payload-descriptions for details. +struct DefaultAdAttributionFetcher: AdAttributionFetcher { + + typealias TokenGetter = () throws -> String + + private let tokenGetter: TokenGetter + private let urlSession: URLSession + private let retryInterval: TimeInterval + + init(tokenGetter: @escaping TokenGetter = Self.fetchAttributionToken, + urlSession: URLSession = .shared, + retryInterval: TimeInterval = .seconds(5)) { + self.tokenGetter = tokenGetter + self.urlSession = urlSession + self.retryInterval = retryInterval + } + + func fetch() async -> AdServicesAttributionResponse? { + guard #available(iOS 14.3, *) else { + return nil + } + + var lastToken: String? + + for _ in 0.. AdServicesAttributionResponse { + let request = createAttributionDataRequest(with: token) + let (data, response) = try await urlSession.data(for: request) + + guard let response = response as? HTTPURLResponse else { + throw AdAttributionFetcherError.invalidResponse + } + + switch response.statusCode { + case 200: + let decoder = JSONDecoder() + let decoded = try decoder.decode(AdServicesAttributionResponse.self, from: data) + + return decoded + case 400: + throw AdAttributionFetcherError.invalidToken + case 404: + throw AdAttributionFetcherError.invalidResponse + default: + throw AdAttributionFetcherError.unknown + } + } + + private func createAttributionDataRequest(with token: String) -> URLRequest { + var request = URLRequest(url: Constant.attributionServiceURL) + request.httpMethod = "POST" + request.setValue("text/plain", forHTTPHeaderField: "Content-Type") + request.httpBody = token.data(using: .utf8) + + return request + } + + private struct Constant { + static let attributionServiceURL = URL(string: "https://api-adservices.apple.com/api/v1/")! + static let maxRetries = 3 + } +} + +extension AdAttributionFetcher { + static func fetchAttributionToken() throws -> String { + if #available(iOS 14.3, *) { + return try AAAttribution.attributionToken() + } else { + throw AdAttributionFetcherError.attributionUnsupported + } + } +} + +struct AdServicesAttributionResponse: Decodable { + let attribution: Bool + let orgId: Int? + let campaignId: Int? + let conversionType: String? + let adGroupId: Int? + let countryOrRegion: String? + let keywordId: Int? + let adId: Int? +} + +enum AdAttributionFetcherError: Error { + case attributionUnsupported + case invalidResponse + case invalidToken + case unknown + + var allowsRetry: Bool { + switch self { + case .invalidToken, .invalidResponse: + return true + case .unknown, .attributionUnsupported: + return false + } + } +} diff --git a/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift b/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift new file mode 100644 index 0000000000..4ea0faa969 --- /dev/null +++ b/DuckDuckGo/AdAttribution/AdAttributionPixelReporter.swift @@ -0,0 +1,96 @@ +// +// AdAttributionPixelReporter.swift +// DuckDuckGo +// +// 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 Core + +protocol PixelFiring { + static func fire(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) async throws +} + +final class AdAttributionPixelReporter { + + static var shared = AdAttributionPixelReporter() + + private var fetcherStorage: AdAttributionReporterStorage + private let attributionFetcher: AdAttributionFetcher + private let pixelFiring: PixelFiring.Type + + init(fetcherStorage: AdAttributionReporterStorage = UserDefaultsAdAttributionReporterStorage(), + attributionFetcher: AdAttributionFetcher = DefaultAdAttributionFetcher(), + pixelFiring: PixelFiring.Type = Pixel.self) { + self.fetcherStorage = fetcherStorage + self.attributionFetcher = attributionFetcher + self.pixelFiring = pixelFiring + } + + @discardableResult + func reportAttributionIfNeeded() async -> Bool { + guard await fetcherStorage.wasAttributionReportSuccessful == false else { + return false + } + + if let attributionData = await self.attributionFetcher.fetch() { + if attributionData.attribution { + let parameters = self.pixelParametersForAttribution(attributionData) + do { + try await pixelFiring.fire(pixel: .appleAdAttribution, withAdditionalParameters: parameters) + } catch { + return false + } + } + + await fetcherStorage.markAttributionReportSuccessful() + + return true + } + + return false + } + + private func pixelParametersForAttribution(_ attribution: AdServicesAttributionResponse) -> [String: String] { + var params: [String: String] = [:] + + params[PixelParameters.adAttributionAdGroupID] = attribution.adGroupId.map(String.init) + params[PixelParameters.adAttributionOrgID] = attribution.orgId.map(String.init) + params[PixelParameters.adAttributionCampaignID] = attribution.campaignId.map(String.init) + params[PixelParameters.adAttributionConversionType] = attribution.conversionType + params[PixelParameters.adAttributionAdGroupID] = attribution.adGroupId.map(String.init) + params[PixelParameters.adAttributionCountryOrRegion] = attribution.countryOrRegion + params[PixelParameters.adAttributionKeywordID] = attribution.keywordId.map(String.init) + params[PixelParameters.adAttributionAdID] = attribution.adId.map(String.init) + + return params + } +} + +extension Pixel: PixelFiring { + static func fire(pixel: Event, withAdditionalParameters params: [String: String]) async throws { + + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + Pixel.fire(pixel: pixel, withAdditionalParameters: params) { error in + if let error { + continuation.resume(throwing: error) + } else { + continuation.resume() + } + } + } + } +} diff --git a/DuckDuckGo/AdAttribution/AdAttributionReporterStorage.swift b/DuckDuckGo/AdAttribution/AdAttributionReporterStorage.swift new file mode 100644 index 0000000000..c0085e05f9 --- /dev/null +++ b/DuckDuckGo/AdAttribution/AdAttributionReporterStorage.swift @@ -0,0 +1,38 @@ +// +// AdAttributionReporterStorage.swift +// DuckDuckGo +// +// 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 Core +import Foundation + +protocol AdAttributionReporterStorage { + var wasAttributionReportSuccessful: Bool { get async } + + func markAttributionReportSuccessful() async +} + +final class UserDefaultsAdAttributionReporterStorage: AdAttributionReporterStorage { + @MainActor + @UserDefaultsWrapper(key: .appleAdAttributionReportCompleted, defaultValue: false) + var wasAttributionReportSuccessful: Bool + + @MainActor + func markAttributionReportSuccessful() async { + wasAttributionReportSuccessful = true + } +} diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 1f712dd2c6..3bd12868ed 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -335,6 +335,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate { clearDebugWaitlistState() + reportAdAttribution() + AppDependencyProvider.shared.userBehaviorMonitor.handleAction(.reopenApp) return true @@ -387,6 +389,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } #endif + private func reportAdAttribution() { + Task.detached(priority: .background) { + await AdAttributionPixelReporter.shared.reportAttributionIfNeeded() + } + } + func applicationDidBecomeActive(_ application: UIApplication) { guard !testing else { return } diff --git a/DuckDuckGoTests/AdAttributionFetcherTests.swift b/DuckDuckGoTests/AdAttributionFetcherTests.swift new file mode 100644 index 0000000000..28b9c94541 --- /dev/null +++ b/DuckDuckGoTests/AdAttributionFetcherTests.swift @@ -0,0 +1,183 @@ +// +// AdAttributionFetcherTests.swift +// DuckDuckGo +// +// 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 XCTest + +@testable import DuckDuckGo +@testable import TestUtils + +final class AdAttributionFetcherTests: XCTestCase { + + private let mockSession: URLSession = { + let configuration = URLSessionConfiguration.default + configuration.protocolClasses = [MockURLProtocol.self] + let session = URLSession(configuration: configuration) + + return session + }() + + override func setUpWithError() throws { + MockURLProtocol.requestHandler = MockURLProtocol.defaultHandler + } + + override func tearDownWithError() throws { + MockURLProtocol.requestHandler = nil + } + + func testMakesRequestWithToken() async throws { + let testToken = "foo" + let sut = DefaultAdAttributionFetcher(tokenGetter: { testToken }, urlSession: mockSession, retryInterval: .leastNonzeroMagnitude) + + _ = await sut.fetch() + + let requestStream = try XCTUnwrap(MockURLProtocol.lastRequest?.httpBodyStream) + let requestBody = try Data(reading: requestStream) + + XCTAssertEqual(String(data: requestBody, encoding: .utf8), testToken) + } + + func testRetriesRequest() async throws { + let testToken = "foo" + let sut = DefaultAdAttributionFetcher(tokenGetter: { testToken }, urlSession: mockSession, retryInterval: .leastNonzeroMagnitude) + let retryExpectation = XCTestExpectation() + retryExpectation.expectedFulfillmentCount = 3 + retryExpectation.assertForOverFulfill = true + + MockURLProtocol.requestHandler = { request in + retryExpectation.fulfill() + let handler = MockURLProtocol.handler(with: 404) + return try handler(request) + } + + _ = await sut.fetch() + + let requestStream = try XCTUnwrap(MockURLProtocol.lastRequest?.httpBodyStream) + let requestBody = try Data(reading: requestStream) + + XCTAssertEqual(String(data: requestBody, encoding: .utf8), testToken) + + await fulfillment(of: [retryExpectation]) + } + + func testRefreshesTokenOnRetry() async throws { + let retryExpectation = XCTestExpectation() + retryExpectation.expectedFulfillmentCount = 3 + retryExpectation.assertForOverFulfill = true + + let refreshExpectation = XCTestExpectation() + + let testToken = "foo" + let sut = DefaultAdAttributionFetcher(tokenGetter: { + refreshExpectation.fulfill() + return testToken + }, urlSession: mockSession, retryInterval: .leastNonzeroMagnitude) + + MockURLProtocol.requestHandler = { request in + retryExpectation.fulfill() + let handler = MockURLProtocol.handler(with: 400) + return try handler(request) + } + + _ = await sut.fetch() + + let requestStream = try XCTUnwrap(MockURLProtocol.lastRequest?.httpBodyStream) + let requestBody = try Data(reading: requestStream) + + XCTAssertEqual(String(data: requestBody, encoding: .utf8), testToken) + + await fulfillment(of: [retryExpectation]) + } + + func testDoesNotRetry_WhenUnrecoverable() async throws { + let testToken = "foo" + let sut = DefaultAdAttributionFetcher(tokenGetter: { testToken }, urlSession: mockSession, retryInterval: .leastNonzeroMagnitude) + let noRetryExpectation = XCTestExpectation() + noRetryExpectation.expectedFulfillmentCount = 1 + noRetryExpectation.assertForOverFulfill = true + + MockURLProtocol.requestHandler = { request in + noRetryExpectation.fulfill() + let handler = MockURLProtocol.handler(with: 500) + return try handler(request) + } + + _ = await sut.fetch() + + let requestStream = try XCTUnwrap(MockURLProtocol.lastRequest?.httpBodyStream) + let requestBody = try Data(reading: requestStream) + + XCTAssertEqual(String(data: requestBody, encoding: .utf8), testToken) + + await fulfillment(of: [noRetryExpectation]) + } + + func testRespectsRetryInterval() async throws { + let testToken = "foo" + let sut = DefaultAdAttributionFetcher(tokenGetter: { testToken }, urlSession: mockSession, retryInterval: .milliseconds(30)) + + MockURLProtocol.requestHandler = { request in + let handler = MockURLProtocol.handler(with: 404) + return try handler(request) + } + + let startTime = Date() + _ = await sut.fetch() + + XCTAssertGreaterThanOrEqual(Date().timeIntervalSince(startTime), .milliseconds(90)) + } +} + +private extension MockURLProtocol { + typealias RequestHandler = (URLRequest) throws -> (HTTPURLResponse, Data?) + + static func handler(with statusCode: Int, data: Data? = nil) -> RequestHandler { + return { request in + (HTTPURLResponse(url: request.url!, statusCode: statusCode, httpVersion: nil, headerFields: nil)!, data) + } + } + + static let defaultHandler = handler(with: 300) +} + +private extension Data { + init(reading input: InputStream, size: Int = 1024) throws { + self.init() + input.open() + defer { + input.close() + } + + let bufferSize = size + let buffer = UnsafeMutablePointer.allocate(capacity: bufferSize) + defer { + buffer.deallocate() + } + while input.hasBytesAvailable { + let read = input.read(buffer, maxLength: bufferSize) + if read < 0 { + // Stream error occured + throw input.streamError! + } else if read == 0 { + // EOF + break + } + self.append(buffer, count: read) + } + } +} diff --git a/DuckDuckGoTests/AdAttributionPixelReporterTests.swift b/DuckDuckGoTests/AdAttributionPixelReporterTests.swift new file mode 100644 index 0000000000..97eef8546e --- /dev/null +++ b/DuckDuckGoTests/AdAttributionPixelReporterTests.swift @@ -0,0 +1,206 @@ +// +// AdAttributionPixelReporterTests.swift +// DuckDuckGo +// +// 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 Core +import XCTest + +@testable import DuckDuckGo + +final class AdAttributionPixelReporterTests: XCTestCase { + + private var attributionFetcher: AdAttributionFetcherMock! + private var fetcherStorage: AdAttributionReporterStorageMock! + + override func setUpWithError() throws { + attributionFetcher = AdAttributionFetcherMock() + fetcherStorage = AdAttributionReporterStorageMock() + } + + override func tearDownWithError() throws { + attributionFetcher = nil + fetcherStorage = nil + PixelFiringMock.tearDown() + } + + func testReportsAttribution() async { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: true) + + let result = await sut.reportAttributionIfNeeded() + + XCTAssertEqual(PixelFiringMock.lastPixel, .appleAdAttribution) + XCTAssertTrue(result) + } + + func testReportsOnce() async { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: true) + + await fetcherStorage.markAttributionReportSuccessful() + let result = await sut.reportAttributionIfNeeded() + + XCTAssertNil(PixelFiringMock.lastPixel) + XCTAssertFalse(result) + } + + func testPixelname() async { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: true) + + let result = await sut.reportAttributionIfNeeded() + + XCTAssertEqual(PixelFiringMock.lastPixel?.name, "m_apple-ad-attribution") + XCTAssertTrue(result) + } + + func testPixelAttributesNaming() async throws { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: true) + + await sut.reportAttributionIfNeeded() + + let pixelAttributes = try XCTUnwrap(PixelFiringMock.lastParams) + + XCTAssertEqual(pixelAttributes["org_id"], "1") + XCTAssertEqual(pixelAttributes["campaign_id"], "2") + XCTAssertEqual(pixelAttributes["conversion_type"], "conversionType") + XCTAssertEqual(pixelAttributes["ad_group_id"], "3") + XCTAssertEqual(pixelAttributes["country_or_region"], "countryOrRegion") + XCTAssertEqual(pixelAttributes["keyword_id"], "4") + XCTAssertEqual(pixelAttributes["ad_id"], "5") + } + + func testPixelAttributes_WhenPartialAttributionData() async throws { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse( + attribution: true, + orgId: 1, + campaignId: 2, + conversionType: "conversionType", + adGroupId: nil, + countryOrRegion: nil, + keywordId: nil, + adId: nil + ) + + await sut.reportAttributionIfNeeded() + + let pixelAttributes = try XCTUnwrap(PixelFiringMock.lastParams) + + XCTAssertEqual(pixelAttributes["org_id"], "1") + XCTAssertEqual(pixelAttributes["campaign_id"], "2") + XCTAssertEqual(pixelAttributes["conversion_type"], "conversionType") + XCTAssertNil(pixelAttributes["ad_group_id"]) + XCTAssertNil(pixelAttributes["country_or_region"]) + XCTAssertNil(pixelAttributes["keyword_id"]) + XCTAssertNil(pixelAttributes["ad_id"]) + } + + func testPixelNotFiredAndMarksReport_WhenAttributionFalse() async { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: false) + + let result = await sut.reportAttributionIfNeeded() + + XCTAssertNil(PixelFiringMock.lastPixel) + XCTAssertTrue(fetcherStorage.wasAttributionReportSuccessful) + XCTAssertTrue(result) + } + + func testPixelNotFiredAndReportNotMarked_WhenAttributionUnavailable() async { + let sut = createSUT() + attributionFetcher.fetchResponse = nil + + let result = await sut.reportAttributionIfNeeded() + + XCTAssertNil(PixelFiringMock.lastPixel) + XCTAssertFalse(fetcherStorage.wasAttributionReportSuccessful) + XCTAssertFalse(result) + } + + func testDoesNotMarkSuccessful_WhenPixelFiringFailed() async { + let sut = createSUT() + attributionFetcher.fetchResponse = AdServicesAttributionResponse(attribution: true) + PixelFiringMock.expectedFireError = NSError(domain: "PixelFailure", code: 1) + + let result = await sut.reportAttributionIfNeeded() + + XCTAssertFalse(fetcherStorage.wasAttributionReportSuccessful) + XCTAssertFalse(result) + } + + private func createSUT() -> AdAttributionPixelReporter { + AdAttributionPixelReporter(fetcherStorage: fetcherStorage, + attributionFetcher: attributionFetcher, + pixelFiring: PixelFiringMock.self) + } +} + +class AdAttributionReporterStorageMock: AdAttributionReporterStorage { + func markAttributionReportSuccessful() async { + wasAttributionReportSuccessful = true + } + + private(set) var wasAttributionReportSuccessful: Bool = false +} + +class AdAttributionFetcherMock: AdAttributionFetcher { + var fetchResponse: AdServicesAttributionResponse? + func fetch() async -> AdServicesAttributionResponse? { + fetchResponse + } +} + +final actor PixelFiringMock: PixelFiring { + static var expectedFireError: Error? + + static var lastParams: [String: String]? + static var lastPixel: Pixel.Event? + static func fire(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) async throws { + lastParams = params + lastPixel = pixel + + if let expectedFireError { + throw expectedFireError + } + } + + static func tearDown() { + lastParams = nil + lastPixel = nil + expectedFireError = nil + } + + private init() {} +} + +extension AdServicesAttributionResponse { + init(attribution: Bool) { + self.init( + attribution: attribution, + orgId: 1, + campaignId: 2, + conversionType: "conversionType", + adGroupId: 3, + countryOrRegion: "countryOrRegion", + keywordId: 4, + adId: 5 + ) + } +} From fb2b820abd1028e58259c36c5cd41aaaa945d0ca Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 1 Mar 2024 08:41:20 -0800 Subject: [PATCH 072/245] Upgrade to Xcode 15.2 (#2517) Task/Issue URL: https://app.asana.com/0/414235014887631/1206726782619250/f Tech Design URL: CC: Description: This PR updates the repo to use Xcode 15.2. --- .xcode-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.xcode-version b/.xcode-version index adbc6d2b1b..dafb659a69 100644 --- a/.xcode-version +++ b/.xcode-version @@ -1 +1 @@ -15.1 +15.2 From d761d615fcff21cd12b3e99f3a75b7ee333463ca Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 1 Mar 2024 19:41:41 +0100 Subject: [PATCH 073/245] 15. Subscription sign-out notification observers and Design Feedback (#2528) Task/Issue URL: https://app.asana.com/0/414235014887631/1206458412095677/f Description: - Design review feedback updates (copy, layout fixes, etc) - Removes debug flags on alpha builds (cc @graeme) - Add notification observers for signOut on notifications --- DuckDuckGo.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/swiftpm/Package.resolved | 9 -- DuckDuckGo/SettingsSubscriptionView.swift | 1 - DuckDuckGo/SettingsViewModel.swift | 27 ++++-- .../SubscriptionSettingsViewModel.swift | 16 +++- .../Views/SubscriptionRestoreView.swift | 93 ++++++++++++------- .../Views/SubscriptionSettingsView.swift | 8 +- DuckDuckGo/UserText.swift | 3 +- DuckDuckGo/en.lproj/Localizable.strings | 5 +- 9 files changed, 109 insertions(+), 61 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c94784731f..f58281d290 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2467,6 +2467,7 @@ D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; + D6CF463D2B9252DB000782AD /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadView.swift; sourceTree = ""; }; @@ -3710,6 +3711,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + D6CF463D2B9252DB000782AD /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, @@ -9365,10 +9367,6 @@ GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; @@ -9380,7 +9378,7 @@ ONLY_ACTIVE_ARCH = YES; OTHER_LDFLAGS = "-ld_classic"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG NETWORK_PROTECTION ALPHA SUBSCRIPTION"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "NETWORK_PROTECTION ALPHA SUBSCRIPTION"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; VALID_ARCHS = "$(ARCHS_STANDARD_64_BIT)"; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 018b6e092f..13f0d21cea 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,15 +18,6 @@ "version" : "3.0.0" } }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", - "state" : { - "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", - "version" : "114.1.0" - } - }, { "identity" : "cocoaasyncsocket", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index c409974387..c758793012 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -56,7 +56,6 @@ struct SettingsSubscriptionView: View { private var manageSubscriptionView: some View { Text(UserText.settingsPProManageSubscription) .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent)) } private var purchaseSubscriptionView: some View { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 2121cbc65e..e2aa84904e 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -49,6 +49,7 @@ final class SettingsViewModel: ObservableObject { private let voiceSearchHelper: VoiceSearchHelperProtocol #if SUBSCRIPTION private var accountManager: AccountManager + private var signOutObserver: Any? #endif @@ -215,7 +216,14 @@ final class SettingsViewModel: ObservableObject { self.accountManager = accountManager self.voiceSearchHelper = voiceSearchHelper self.onAppearNavigationTarget = navigateOnAppearDestination + + setupNotificationObservers() + } + + deinit { + signOutObserver = nil } + #else // MARK: Default Init init(state: SettingsState? = nil, @@ -233,7 +241,7 @@ final class SettingsViewModel: ObservableObject { // MARK: Private methods extension SettingsViewModel { - // This manual (re)initialzation will go away once appSettings and + // This manual (re)initialization will go away once appSettings and // other dependencies are observable (Such as AppIcon and netP) // and we can use subscribers (Currently called from the view onAppear) private func initState() { @@ -339,12 +347,8 @@ extension SettingsViewModel { // Fetch available subscriptions from the backend (or sign out) switch await SubscriptionService.getSubscriptionDetails(token: token) { - case .success(let response) where !response.isSubscriptionActive: - - // Account is active but there's not a valid subscription - signOutUser() - case .success(let response): + case .success(let response) where response.isSubscriptionActive: // Cache Subscription state cacheSubscriptionState(active: true) @@ -365,6 +369,7 @@ extension SettingsViewModel { } default: + // Account is active but there's not a valid subscription / entitlements signOutUser() } } @@ -390,6 +395,16 @@ extension SettingsViewModel { self?.state.subscription.canPurchase = !products.isEmpty }.store(in: &cancellables) } + + private func setupNotificationObservers() { + signOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { [weak self] _ in + if #available(iOS 15.0, *) { + guard let strongSelf = self else { return } + Task { await strongSelf.setupSubscriptionEnvironment() } + } + } + } + #endif #if NETWORK_PROTECTION diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index d798eac708..6ad599d49f 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -36,6 +36,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { let accountManager: AccountManager private var subscriptionUpdateTimer: Timer? + private var signOutObserver: Any? + @Published var subscriptionDetails: String = "" @Published var subscriptionType: String = "" @Published var shouldDisplayRemovalNotice: Bool = false @@ -45,6 +47,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { self.accountManager = accountManager Task { await fetchAndUpdateSubscriptionDetails() } setupSubscriptionUpdater() + setupNotificationObservers() } private var dateFormatter: DateFormatter = { @@ -76,6 +79,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { } } + private func setupNotificationObservers() { + signOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { [weak self] _ in + DispatchQueue.main.async { + self?.shouldDismissView = true + } + } + } + private func setupSubscriptionUpdater() { subscriptionUpdateTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { [weak self] _ in guard let strongSelf = self else { return } @@ -94,7 +105,9 @@ final class SubscriptionSettingsViewModel: ObservableObject { func removeSubscription() { AccountManager().signOut() - ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation) + let messageView = ActionMessageView() + ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, + presentationLocation: .withoutBottomBar) } func manageSubscription() { @@ -120,6 +133,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { deinit { subscriptionUpdateTimer?.invalidate() + signOutObserver = nil } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 82825e2a74..ac67117332 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -37,27 +37,38 @@ struct SubscriptionRestoreView: View { static let appleIDIcon = "Platform-Apple-16" static let emailIcon = "Email-16" static let headerLineSpacing = 10.0 + static let viewStackSpacing = 20.0 + static let footerLineSpacing = 7.0 static let openIndicator = "chevron.up" static let closedIndicator = "chevron.down" + static let cornerRadius = 12.0 static let buttonCornerRadius = 8.0 static let buttonInsets = EdgeInsets(top: 10.0, leading: 16.0, bottom: 10.0, trailing: 16.0) static let cellLineSpacing = 12.0 - static let cellPadding = 8.0 - static let headerPadding = EdgeInsets(top: 16.0, leading: 16.0, bottom: 0, trailing: 16.0) + static let cellPadding = 20.0 + static let headerPadding = EdgeInsets(top: 16.0, leading: 30.0, bottom: 0, trailing: 30.0) + static let viewPadding: CGFloat = 18.0 + static let listPadding = EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30) + static let borderWidth: CGFloat = 1.0 } var body: some View { ZStack { - VStack { + VStack(spacing: Constants.viewStackSpacing) { // Email Activation View Hidden link NavigationLink(destination: SubscriptionEmailView(isAddingDevice: viewModel.isAddingDevice), isActive: $isActive) { EmptyView() }.isDetailLink(false) - + headerView - listView - } + optionsView + footerView + + Spacer() + }.background(Color(designSystemColor: .background)) + + .navigationTitle(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceTitle : UserText.subscriptionActivate) .navigationBarBackButtonHidden(viewModel.transactionStatus != .idle) .applyInsetGroupedListStyle() @@ -95,7 +106,7 @@ struct SubscriptionRestoreView: View { HStack { Image(icon) Text(text) - .daxBodyRegular() + .daxSubheadSemibold() .foregroundColor(Color(designSystemColor: .textPrimary)) } ) @@ -143,7 +154,7 @@ struct SubscriptionRestoreView: View { }) .background(Color(designSystemColor: .accent)) .cornerRadius(Constants.buttonCornerRadius) - .padding(.top, Constants.cellPadding) + .padding(.top, Constants.cellLineSpacing) ) } @@ -162,22 +173,23 @@ struct SubscriptionRestoreView: View { private var headerView: some View { VStack(spacing: Constants.headerLineSpacing) { - Image(Constants.heroImage) + Image(Constants.heroImage).padding(.bottom, Constants.cellLineSpacing) Text(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceHeaderTitle : UserText.subscriptionActivateTitle) .daxHeadline() .multilineTextAlignment(.center) .foregroundColor(Color(designSystemColor: .textPrimary)) - Text(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceDescription : UserText.subscriptionActivateDescription) + Text(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceDescription : UserText.subscriptionActivateHeaderDescription) .daxFootnoteRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) .multilineTextAlignment(.center) - }.padding(Constants.headerPadding) + } + .padding(Constants.headerPadding) } @ViewBuilder private var footerView: some View { if !viewModel.isAddingDevice { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: Constants.footerLineSpacing) { Text(UserText.subscriptionActivateDescription) .daxFootnoteRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) @@ -188,32 +200,38 @@ struct SubscriptionRestoreView: View { .daxFootnoteSemibold() .foregroundColor(Color(designSystemColor: .accent)) }) - }.padding(.top, Constants.headerLineSpacing) + } + .padding(Constants.listPadding) } } - private var listView: some View { - List { - Section(footer: footerView) { - ForEach(Array(zip(listItems.indices, listItems)), id: \.1.id) { _, item in - VStack(alignment: .leading, spacing: Constants.cellLineSpacing) { - HStack { - item.content - Spacer() - if listItems.count > 1 { - Image(systemName: expandedItemId == item.id ? Constants.openIndicator : Constants.closedIndicator) - .foregroundColor(Color(designSystemColor: .textPrimary)) - } - } - .contentShape(Rectangle()) - .onTapGesture { - expandedItemId = expandedItemId == item.id ? 0 : item.id - } - if expandedItemId == item.id { - item.expandedContent + private var optionsView: some View { + VStack { + ForEach(Array(zip(listItems.indices, listItems)), id: \.1.id) { _, item in + VStack(alignment: .leading, spacing: Constants.cellLineSpacing) { + HStack { + item.content + Spacer() + if listItems.count > 1 { + Image(systemName: expandedItemId == item.id ? Constants.openIndicator : Constants.closedIndicator) + .foregroundColor(Color(designSystemColor: .textPrimary)) } - }.padding(Constants.cellPadding) + } + .contentShape(Rectangle()) + .onTapGesture { + expandedItemId = expandedItemId == item.id ? 0 : item.id + } + if expandedItemId == item.id { + item.expandedContent + } } + .padding(Constants.cellPadding) + .background(Color(designSystemColor: .panel)) + .overlay( + RoundedRectangle(cornerRadius: Constants.cornerRadius) + .stroke(Color(designSystemColor: .lines), lineWidth: Constants.borderWidth) + ) + .padding(Constants.listPadding) } } } @@ -264,5 +282,14 @@ struct SubscriptionRestoreView: View { let content: AnyView let expandedContent: AnyView } + +} + +@available(iOS 15.0, *) +struct SubscriptionRestoreView_Previews: PreviewProvider { + static var previews: some View { + SubscriptionRestoreView() + .previewDevice("iPhone 12") + } } #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index ee294c65d8..2f6a93f513 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -35,7 +35,7 @@ struct SubscriptionSettingsView: View { @StateObject var sceneEnvironment = SceneEnvironment() @ViewBuilder - private var listView: some View { + private var optionsView: some View { List { Section { VStack(alignment: .center, spacing: 7) { @@ -54,6 +54,7 @@ struct SubscriptionSettingsView: View { SettingsCustomCell(content: { Text(UserText.subscriptionChangePlan) .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent)) }, action: { Task { viewModel.manageSubscription() } }, isButton: true) @@ -65,7 +66,6 @@ struct SubscriptionSettingsView: View { SettingsCustomCell(content: { Text(UserText.subscriptionAddDeviceButton) .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent)) }) } @@ -119,10 +119,10 @@ struct SubscriptionSettingsView: View { var body: some View { Group { if #available(iOS 16.0, *) { - listView + optionsView .scrollDisabled(true) } else { - listView + optionsView } } .navigationBarTitleDisplayMode(.inline) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index a9e746b13a..ca0a7e8886 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1023,7 +1023,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionRemoveFromDevice = NSLocalizedString("subscription.remove.from.device.button", value: "Remove From This Device", comment: "Remove from this device button") public static let subscriptionManageTitle = NSLocalizedString("subscription.manage.title", value: "Subscription", comment: "Header for the subscription section") public static let subscriptionManagePlan = NSLocalizedString("subscription.manage.plan", value: "Manage Plan", comment: "Manage Plan header") - public static let subscriptionChangePlan = NSLocalizedString("subscription.change.plan", value: "Change Plan Or Billing", comment: "Change plan or billing title") + public static let subscriptionChangePlan = NSLocalizedString("subscription.change.plan", value: "Change Plan or Billing", comment: "Change plan or billing title") public static let subscriptionHelpAndSupport = NSLocalizedString("subscription.help", value: "Help and support", comment: "Help and support Section header") public static let subscriptionFAQ = NSLocalizedString("subscription.faq", value: "Privacy Pro FAQ", comment: "FAQ Button") public static let subscriptionFAQFooter = NSLocalizedString("subscription.faq.description", value: "Get answers to frequently asked questions about Privacy Pro in our help pages.", comment: "FAQ Description") @@ -1039,6 +1039,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionActivate = NSLocalizedString("subscription.activate", value: "Activate Subscription", comment: "Subscription Activation Window Title") public static let subscriptionActivateTitle = NSLocalizedString("subscription.activate.title", value: "Activate your subscription on this device", comment: "Subscription Activation Title") public static let subscriptionActivateDescription = NSLocalizedString("subscription.activate.description", value: "Your subscription is automatically available in DuckDuckGo on any device signed in to your Apple ID.", comment: "Subscription Activation Info") + public static let subscriptionActivateHeaderDescription = NSLocalizedString("subscription.activate..header.description", value: "Access your Privacy Pro subscription on this device via Apple ID or an email address.", comment: "Subscription Activation Info") public static let subscriptionActivateAppleID = NSLocalizedString("subscription.activate.appleid", value: "Apple ID", comment: "Apple ID option for activation") public static let subscriptionActivateAppleIDButton = NSLocalizedString("subscription.activate.appleid.button", value: "Restore Purchase", comment: "Button text for restoring purchase via Apple ID") public static let subscriptionActivateAppleIDDescription = NSLocalizedString("subscription.activate.appleid.description", value: "Restore your purchase to activate your subscription on this device.", comment: "Description for Apple ID activation") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 9229afac4d..cc817d9c34 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1908,6 +1908,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Subscription Activation Window Title */ "subscription.activate" = "Activate Subscription"; +/* Subscription Activation Info */ +"subscription.activate..header.description" = "Access your Privacy Pro subscription on this device via Apple ID or an email address."; + /* Restore button title for Email */ "subscription.activate.add.email.button" = "Add Email"; @@ -1990,7 +1993,7 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.cancel.message" = "Your subscription has been removed from this device."; /* Change plan or billing title */ -"subscription.change.plan" = "Change Plan Or Billing"; +"subscription.change.plan" = "Change Plan or Billing"; /* Navigation Button for closing subscription view */ "subscription.close" = "Close"; From fa3ca3f7481492d0845c738cbebf508313007f71 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 1 Mar 2024 11:49:35 -0800 Subject: [PATCH 074/245] Remove local BSK reference (#2529) Task/Issue URL: https://app.asana.com/0/414235014887631/1206742300407972/f Tech Design URL: CC: Description: This PR removes a local BSK reference. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 -- .../xcshareddata/swiftpm/Package.resolved | 9 +++++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f58281d290..992037d17f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2467,7 +2467,6 @@ D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; - D6CF463D2B9252DB000782AD /* BrowserServicesKit */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = BrowserServicesKit; path = ../BrowserServicesKit; sourceTree = ""; }; D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadView.swift; sourceTree = ""; }; @@ -3711,7 +3710,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - D6CF463D2B9252DB000782AD /* BrowserServicesKit */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 13f0d21cea..018b6e092f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -18,6 +18,15 @@ "version" : "3.0.0" } }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", + "state" : { + "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", + "version" : "114.1.0" + } + }, { "identity" : "cocoaasyncsocket", "kind" : "remoteSourceControl", From 4a220f879267dad7f6e629a3937d65015f800e69 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 4 Mar 2024 13:09:11 +0000 Subject: [PATCH 075/245] if dax dialogs are showing then dismiss (#2506) --- .github/workflows/end-to-end.yml | 1 + .../data_clearing_tests/02_duckduckgo_settings.yml | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index c0a2e39498..5cef39c0a7 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -3,6 +3,7 @@ name: End-to-End tests on: schedule: - cron: '0 4 * * *' # run at 4 AM UTC + workflow_dispatch: jobs: end-to-end-tests: diff --git a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml index 9de815dc35..e2da0f3e3a 100644 --- a/.maestro/data_clearing_tests/02_duckduckgo_settings.yml +++ b/.maestro/data_clearing_tests/02_duckduckgo_settings.yml @@ -5,9 +5,10 @@ tags: --- # Set up +- clearKeychain - clearState - launchApp -- runFlow: +- runFlow: when: visible: text: "Let’s Do It!" @@ -22,6 +23,13 @@ tags: - inputText: "privacy blogs" - pressKey: Enter +# Dismiss Dax Dialogs if visible +- runFlow: + when: + visible: "Phew!" + commands: + - tapOn: "Phew!" + # Change settings - tapOn: "Safe search: moderate ▼" - tapOn: "Off" From f77f1a7666bdadcc815afe001ffae76b0df277a5 Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Mon, 4 Mar 2024 18:14:16 +0000 Subject: [PATCH 076/245] Release 7.111.0-0 (#2534) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 190 ++++++++++++++---- DuckDuckGo.xcodeproj/project.pbxproj | 56 +++--- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 5 files changed, 185 insertions(+), 69 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 6921fbe607..96f77f5f65 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.110.0 +MARKETING_VERSION = 7.111.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 2eb52672b6..37ce38ac13 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"20ceae04600e9d3c46898ac4471fcd70\"" - public static let embeddedDataSHA = "987f63a393724a34fe9d190c335f14b58398d2e9e02ccf38b84a7bd57c37b8ab" + public static let embeddedDataETag = "\"a427a69043b2baa27604bc10edb13de1\"" + public static let embeddedDataSHA = "1d5c2e4113713fbf02bc617fc689981604ea53be172569a9fd744054b7355c39" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index eaba26e1a1..d1f5828cd3 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1708953918019, + "version": 1709563256742, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -71,15 +71,15 @@ { "domain": "www.audiosciencereview.com" }, - { - "domain": "golf.com" - }, { "domain": "thehustle.co" }, { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -98,6 +98,7 @@ "^https?:\\/\\/\\S+ampproject\\.org\\/\\S\\/s\\/(\\S+)$" ], "keywords": [ + "amp=", "=amp", "&", "amp&", @@ -113,7 +114,7 @@ ] }, "state": "enabled", - "hash": "369b8aac2cb358e0df3a828c1e3b9a29" + "hash": "a92fae2cdccf479cc1bbe840cd32627b" }, "androidBrowserConfig": { "exceptions": [], @@ -263,6 +264,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -271,6 +275,9 @@ }, { "domain": "sundancecatalog.com" + }, + { + "domain": "instagram.com" } ], "settings": { @@ -298,7 +305,7 @@ } } }, - "hash": "7b3ce9784ee7348bb7a07d1868c0f3ae" + "hash": "b72f756b119c762183d04867e8927892" }, "autofill": { "exceptions": [ @@ -956,6 +963,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -981,13 +991,16 @@ } }, "state": "disabled", - "hash": "36e8971fa9bb204b78a5929a14a108dd" + "hash": "9c70121360bcdfeb63770d8d9aeee770" }, "clickToPlay": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1008,7 +1021,7 @@ } }, "state": "disabled", - "hash": "4390af06f967ef97a827aeab0ac0d1ca" + "hash": "ba97e20bd75a4dcd4ef376ec9b7fccc1" }, "clientBrandHint": { "exceptions": [], @@ -1039,6 +1052,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1049,7 +1065,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "e37447d42ee8194f185e35e40f577f41" + "hash": "910e25ffe4d683b3c708a1578d097a16" }, "cookie": { "settings": { @@ -1094,6 +1110,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -1105,7 +1124,7 @@ } ], "state": "disabled", - "hash": "37a27966915571085613911b47e6e2eb" + "hash": "7c7ceca9eeb664059750ea96938669b0" }, "customUserAgent": { "settings": { @@ -1229,6 +1248,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -3977,13 +3999,16 @@ ] }, "state": "enabled", - "hash": "01a63fb9be9c1708398761f62f5f9598" + "hash": "70c18c8d5c08210844cb238822888fd7" }, "exceptionHandler": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -3995,7 +4020,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "fingerprintingAudio": { "state": "disabled", @@ -4006,6 +4031,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4016,7 +4044,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "f25a8f2709e865c2bd743828c7ee2f77" + "hash": "40b13d6ca36cd3de287345ab9e5839fb" }, "fingerprintingBattery": { "exceptions": [ @@ -4026,6 +4054,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4037,7 +4068,7 @@ } ], "state": "enabled", - "hash": "440f8d663d59430c93d66208655d9238" + "hash": "038608803499bebc30460a84ed27579f" }, "fingerprintingCanvas": { "settings": { @@ -4131,6 +4162,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4142,7 +4176,7 @@ } ], "state": "disabled", - "hash": "ea4c565bae27996f0d651300d757594c" + "hash": "98b5e91ff539dfb6c81699e32b76f70c" }, "fingerprintingHardware": { "settings": { @@ -4188,6 +4222,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4199,7 +4236,7 @@ } ], "state": "enabled", - "hash": "46fbcd4738329731c1b11e88e3afcb7b" + "hash": "ed0d208ef9ffcba9851eddf68a005583" }, "fingerprintingScreenSize": { "settings": { @@ -4242,6 +4279,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4253,7 +4293,7 @@ } ], "state": "enabled", - "hash": "0fb22f84b750e0d29bad55bd95d9ce2b" + "hash": "264749fcf7f5e7e03478bb6f0df4a48a" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -4269,6 +4309,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4280,13 +4323,16 @@ } ], "state": "enabled", - "hash": "f1632b92379847c92c95bcffefbc1bd2" + "hash": "c8f4dcd850359636b47ebc31a26f1f1d" }, "googleRejected": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4298,7 +4344,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "gpc": { "state": "enabled", @@ -4336,6 +4382,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4358,7 +4407,7 @@ "privacy-test-pages.site" ] }, - "hash": "549a6e76edaf16c1fffced31b97e9553" + "hash": "d1dd05d2cbbb9425a925cc162aaa681f" }, "harmfulApis": { "settings": { @@ -4463,6 +4512,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4474,7 +4526,7 @@ } ], "state": "disabled", - "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" + "hash": "9d0f5f4f8c02e79246e2d809cada2fdb" }, "history": { "state": "enabled", @@ -4496,6 +4548,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4506,7 +4561,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "f772808ed34cc9ea8cbcbb7cdaf74429" + "hash": "ea6d5ad048e35c75c451bff6fe58cb11" }, "incontextSignup": { "exceptions": [], @@ -4544,6 +4599,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4562,7 +4620,7 @@ ] }, "state": "enabled", - "hash": "698de7b963d7d7942c5c5d1e986bb1b1" + "hash": "f8dc40f1f5687f403f381452d66eb0d0" }, "networkProtection": { "state": "enabled", @@ -4590,6 +4648,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4601,7 +4662,7 @@ } ], "state": "disabled", - "hash": "841fa92b9728c9754f050662678f82c7" + "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, "notificationPermissions": { "exceptions": [], @@ -4616,10 +4677,20 @@ "rollout": { "steps": [] } + }, + "toggleReports": { + "state": "internal", + "rollout": { + "steps": [ + { + "percent": 5 + } + ] + } } }, - "state": "disabled", - "hash": "dede7e70939822f5ecb9eb5fae577fa3" + "state": "enabled", + "hash": "0d76cb4a367fc6738f7c4aa6a66f0a04" }, "privacyProtectionsPopup": { "state": "disabled", @@ -4649,6 +4720,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4660,7 +4734,7 @@ } ], "state": "disabled", - "hash": "0d3df0f7c24ebde89d2dced4e2d34322" + "hash": "1679be76968fe50858b3cc664b8fcbad" }, "requestFilterer": { "state": "disabled", @@ -4668,6 +4742,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4681,7 +4758,7 @@ "settings": { "windowInMs": 0 }, - "hash": "0fff8017d8ea4b5609b8f5c110be1401" + "hash": "219a51a9aafbc9c1bae4bad55d7ce437" }, "runtimeChecks": { "state": "disabled", @@ -4689,6 +4766,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4700,13 +4780,16 @@ } ], "settings": {}, - "hash": "800a19533c728bbec7e31e466f898268" + "hash": "e2246d7c78df2167134e1428b04d51ca" }, "serviceworkerInitiatedRequests": { "exceptions": [ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -4718,7 +4801,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "sync": { "state": "enabled", @@ -5833,6 +5916,12 @@ "domains": [ "channel4.com" ] + }, + { + "rule": "7cbf2.v.fwmrm.net/ad/g/1", + "domains": [ + "6play.fr" + ] } ] }, @@ -7649,6 +7738,12 @@ "domains": [ "fashionnova.com" ] + }, + { + "rule": "rapid-cdn.yottaa.com/rapid/lib/ZfJxptseJcUQIA.js", + "domains": [ + "aviatornation.com" + ] } ] }, @@ -7718,6 +7813,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7728,7 +7826,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "86a7c891d57513e67356a82da2a2aa1d" + "hash": "6768849b4f63b2e635698ac9dde79aa3" }, "trackingCookies1p": { "settings": { @@ -7741,6 +7839,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7752,7 +7853,7 @@ } ], "state": "disabled", - "hash": "4dddf681372a2aea9788090b13db6e6f" + "hash": "bfd8b32efe8d633fe670bf6ab1b00240" }, "trackingCookies3p": { "settings": { @@ -7762,6 +7863,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7773,7 +7877,7 @@ } ], "state": "disabled", - "hash": "841fa92b9728c9754f050662678f82c7" + "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, "trackingParameters": { "exceptions": [ @@ -7783,6 +7887,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7826,7 +7933,7 @@ ] }, "state": "enabled", - "hash": "1df4ca1a649e81401fb5e872212b4dd0" + "hash": "f64c29121e46b2c79c23e8e7efc58c59" }, "userAgentRotation": { "settings": { @@ -7836,6 +7943,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7847,7 +7957,7 @@ } ], "state": "disabled", - "hash": "f65d10dfdf6739feab99a08d42734747" + "hash": "4498ff835bed7ce27ff2a568db599155" }, "voiceSearch": { "exceptions": [], @@ -7859,6 +7969,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7909,7 +8022,7 @@ } ] }, - "hash": "592a1fb6314f04875fc44a66ef7c2433" + "hash": "1b8acba9eed9ba83fdfe0da1e9d8db87" }, "windowsPermissionUsage": { "exceptions": [], @@ -7921,6 +8034,9 @@ { "domain": "earth.google.com" }, + { + "domain": "instructure.com" + }, { "domain": "iscorp.com" }, @@ -7932,7 +8048,7 @@ } ], "state": "disabled", - "hash": "5e792dd491428702bc0104240fbce0ce" + "hash": "2b0b6ee567814d75aa2646d494a45a78" }, "windowsWaitlist": { "exceptions": [], diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 992037d17f..09fa88e566 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8210,7 +8210,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8247,7 +8247,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8339,7 +8339,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8367,7 +8367,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8517,7 +8517,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8543,7 +8543,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8608,7 +8608,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8643,7 +8643,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8677,7 +8677,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8708,7 +8708,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8995,7 +8995,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9026,7 +9026,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9055,7 +9055,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9089,7 +9089,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9120,7 +9120,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9153,11 +9153,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9391,7 +9391,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9418,7 +9418,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9451,7 +9451,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9489,7 +9489,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9525,7 +9525,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9560,11 +9560,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9738,11 +9738,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9771,10 +9771,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index a984845555..d0dff36535 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.110.0 + 7.111.0 Key version Title From 5e056b50106e58a0062a901568dc1ed405521ddb Mon Sep 17 00:00:00 2001 From: Brian Hall Date: Tue, 5 Mar 2024 05:34:41 -0600 Subject: [PATCH 077/245] Bump BrowserServicesKit (#2532) Co-authored-by: Fernando Bunn --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 09fa88e566..d92b4cb973 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -9981,7 +9981,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.1.0; + version = 114.2.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 018b6e092f..d8c92e6a58 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "045a8782c3dbbf79fc088b38120dea1efadc13e1", - "version" : "114.1.0" + "revision" : "457443c10861ea1253383408fe3763f6320d25b4", + "version" : "114.2.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "a3690b7666a3617693383d948cb492513f6aa569", - "version" : "5.0.0" + "revision" : "59752eb7973d3e3b0c23255ff51359f48b343f15", + "version" : "5.2.0" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" From 38d6eb267895fd220b7a935c698c8382cd06eece Mon Sep 17 00:00:00 2001 From: amddg44 Date: Tue, 5 Mar 2024 14:21:12 +0100 Subject: [PATCH 078/245] Autofill support for deleting all passwords (#2497) Task/Issue URL: https://app.asana.com/0/1201462886803403/1206303310228833/f Tech Design URL: CC: Description: Give users the ability to delete all passwords they have saved --- DuckDuckGo.xcodeproj/project.pbxproj | 22 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AuthConfirmationPromptView.swift | 130 ++++++++++++ ...AuthConfirmationPromptViewController.swift | 106 ++++++++++ .../AuthConfirmationPromptViewModel.swift | 60 ++++++ DuckDuckGo/AutofillLoginDetailsView.swift | 4 +- .../AutofillLoginDetailsViewController.swift | 5 +- .../AutofillLoginDetailsViewModel.swift | 14 +- DuckDuckGo/AutofillLoginListViewModel.swift | 54 ++++- ...ofillLoginSettingsListViewController.swift | 177 ++++++++++++++-- DuckDuckGo/AutofillViews.swift | 1 + DuckDuckGo/PasswordGenerationPromptView.swift | 50 ++--- DuckDuckGo/UserText.swift | 27 ++- DuckDuckGo/bg.lproj/Localizable.strings | 32 ++- DuckDuckGo/bg.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/cs.lproj/Localizable.strings | 32 ++- DuckDuckGo/cs.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/da.lproj/Localizable.strings | 32 ++- DuckDuckGo/da.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/de.lproj/Localizable.strings | 32 ++- DuckDuckGo/de.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/el.lproj/Localizable.strings | 32 ++- DuckDuckGo/el.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/en.lproj/Localizable.strings | 30 ++- DuckDuckGo/en.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/es.lproj/Localizable.strings | 32 ++- DuckDuckGo/es.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/et.lproj/Localizable.strings | 32 ++- DuckDuckGo/et.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/fi.lproj/Localizable.strings | 32 ++- DuckDuckGo/fi.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/fr.lproj/Localizable.strings | 34 +++- DuckDuckGo/fr.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/hr.lproj/Localizable.strings | 32 ++- DuckDuckGo/hr.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/hu.lproj/Localizable.strings | 32 ++- DuckDuckGo/hu.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/it.lproj/Localizable.strings | 32 ++- DuckDuckGo/it.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/lt.lproj/Localizable.strings | 32 ++- DuckDuckGo/lt.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/lv.lproj/Localizable.strings | 32 ++- DuckDuckGo/lv.lproj/Localizable.stringsdict | 116 +++++++++++ DuckDuckGo/nb.lproj/Localizable.strings | 32 ++- DuckDuckGo/nb.lproj/Localizable.stringsdict | 103 ++++++++++ DuckDuckGo/nl.lproj/Localizable.strings | 32 ++- DuckDuckGo/nl.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/pl.lproj/Localizable.strings | 32 ++- DuckDuckGo/pl.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/pt.lproj/Localizable.strings | 32 ++- DuckDuckGo/pt.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/ro.lproj/Localizable.strings | 32 ++- DuckDuckGo/ro.lproj/Localizable.stringsdict | 116 +++++++++++ DuckDuckGo/ru.lproj/Localizable.strings | 32 ++- DuckDuckGo/ru.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/sk.lproj/Localizable.strings | 32 ++- DuckDuckGo/sk.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/sl.lproj/Localizable.strings | 32 ++- DuckDuckGo/sl.lproj/Localizable.stringsdict | 130 ++++++++++++ DuckDuckGo/sv.lproj/Localizable.strings | 32 ++- DuckDuckGo/sv.lproj/Localizable.stringsdict | 102 ++++++++++ DuckDuckGo/tr.lproj/Localizable.strings | 32 ++- DuckDuckGo/tr.lproj/Localizable.stringsdict | 102 ++++++++++ .../AutofillLoginListViewModelTests.swift | 190 +++++++++++++++++- DuckDuckGoTests/MockSecureVault.swift | 10 + 65 files changed, 4271 insertions(+), 158 deletions(-) create mode 100644 DuckDuckGo/AuthConfirmationPromptView.swift create mode 100644 DuckDuckGo/AuthConfirmationPromptViewController.swift create mode 100644 DuckDuckGo/AuthConfirmationPromptViewModel.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d92b4cb973..074ad901eb 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -707,6 +707,9 @@ C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */; }; C12726F22A5FF8CB00215B02 /* EmailSignupPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */; }; C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */; }; + C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */; }; + C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */; }; + C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */; }; C14882DA27F2011C00D59F0C /* BookmarksExporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D727F2011C00D59F0C /* BookmarksExporter.swift */; }; C14882DC27F2011C00D59F0C /* BookmarksImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882D927F2011C00D59F0C /* BookmarksImporter.swift */; }; C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C14882E127F20D9A00D59F0C /* BookmarksExporterTests.swift */; }; @@ -2354,6 +2357,9 @@ C12726EF2A5FF89900215B02 /* EmailSignupPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewModel.swift; sourceTree = ""; }; C12726F12A5FF8CB00215B02 /* EmailSignupPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailSignupPromptViewController.swift; sourceTree = ""; }; C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillSettingStatus.swift; sourceTree = ""; }; + C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptView.swift; sourceTree = ""; }; + C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptViewController.swift; sourceTree = ""; }; + C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthConfirmationPromptViewModel.swift; sourceTree = ""; }; C14882D727F2011C00D59F0C /* BookmarksExporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksExporter.swift; sourceTree = ""; }; C14882D927F2011C00D59F0C /* BookmarksImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksImporter.swift; sourceTree = ""; }; C14882E127F20D9A00D59F0C /* BookmarksExporterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksExporterTests.swift; sourceTree = ""; }; @@ -4457,6 +4463,16 @@ name = PasswordGeneration; sourceTree = ""; }; + C1AFFC4B2B8773060060448E /* AuthConfirmation */ = { + isa = PBXGroup; + children = ( + C13F3F672B7F88100083BE40 /* AuthConfirmationPromptView.swift */, + C13F3F692B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift */, + C13F3F6B2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift */, + ); + name = AuthConfirmation; + sourceTree = ""; + }; C1B7B51D28941F160098FD6A /* RemoteMessaging */ = { isa = PBXGroup; children = ( @@ -5593,6 +5609,7 @@ C13B32D12A0E750700A59236 /* AutofillSettingStatus.swift */, 319A370F28299A850079FBCE /* PasswordHider.swift */, 31C70B5428045E3500FB6AD1 /* SecureVaultErrorReporter.swift */, + C1AFFC4B2B8773060060448E /* AuthConfirmation */, F407605328131910006B1E0B /* AutofillLoginUI */, 310C4B4A281B69BC00BA79A9 /* Management */, C17B59552A03AAC40055F2D1 /* PasswordGeneration */, @@ -6860,6 +6877,7 @@ 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, + C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */, @@ -6867,6 +6885,7 @@ F17922DB1E717C8D006E3D97 /* Suggestion.swift in Sources */, 020108A729A6ABF600644F9D /* AppTPToggleView.swift in Sources */, 02A54A982A093126000C8FED /* AppTPHomeViewModel.swift in Sources */, + C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */, F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, 4B5C462A2AF2A6E6002A4432 /* VPNIntents.swift in Sources */, 310742A62848CD780012660B /* BackForwardMenuHistoryItem.swift in Sources */, @@ -6879,6 +6898,7 @@ 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, + C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, EE4BE0092A740BED00CD6AA8 /* ClearTextField.swift in Sources */, @@ -9981,7 +10001,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.2.0; + version = 114.3.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d8c92e6a58..5f6b2bab9d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "457443c10861ea1253383408fe3763f6320d25b4", - "version" : "114.2.0" + "revision" : "1fa06fb43fb0f26fc1a74b710fb1402573264bf5", + "version" : "114.3.0" } }, { diff --git a/DuckDuckGo/AuthConfirmationPromptView.swift b/DuckDuckGo/AuthConfirmationPromptView.swift new file mode 100644 index 0000000000..6c8a157718 --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptView.swift @@ -0,0 +1,130 @@ +// +// AuthConfirmationPromptView.swift +// DuckDuckGo +// +// 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 AuthConfirmationPromptView: View { + + @State var frame: CGSize = .zero + @ObservedObject var viewModel: AuthConfirmationPromptViewModel + @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.verticalSizeClass) var verticalSizeClass + + var body: some View { + GeometryReader { geometry in + makeBodyView(geometry) + } + } + + private func makeBodyView(_ geometry: GeometryProxy) -> some View { + DispatchQueue.main.async { self.frame = geometry.size } + + return VStack { + HStack { + Button { + viewModel.cancelButtonPressed() + } label: { + Text(UserText.actionCancel) + .foregroundColor(Color(designSystemColor: .textSecondary)) + } + Spacer() + } + .padding([.top, .leading], Const.Size.closeButtonPadding) + + Group { + Spacer() + .frame(height: Const.Size.topPadding) + Image + .lock + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Headline(title: UserText.autofillDeleteAllPasswordsAuthenticationPromptTitle) + contentViewSpacer + AutofillViews.PrimaryButton(title: UserText.autofillDeleteAllPasswordsAuthenticationPromptButton, + action: { viewModel.authenticatePressed() }) + .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad + : Const.Size.bottomPadding) + } + .padding(.horizontal, horizontalPadding) + } + .background(GeometryReader { proxy -> Color in + DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } + return Color.clear + }) + .useScrollView(shouldUseScrollView(), minHeight: frame.height) + } + + private var horizontalPadding: CGFloat { + if AutofillViews.isIPhonePortrait(verticalSizeClass, horizontalSizeClass) { + if AutofillViews.isSmallFrame(frame) { + return Const.Size.horizontalPaddingPortraitSmallFrame + } else { + return Const.Size.horizontalPaddingPortrait + } + } else { + return Const.Size.horizontalPadding + } + } + + private var contentViewSpacer: some View { + VStack { + if AutofillViews.isIPhoneLandscape(verticalSizeClass) { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeightLandscape) + } else { + AutofillViews.LegacySpacerView(height: Const.Size.contentSpacerHeight) + } + } + } + + private func shouldUseScrollView() -> Bool { + var useScrollView: Bool = false + + if #available(iOS 16.0, *) { + useScrollView = AutofillViews.contentHeightExceedsScreenHeight(viewModel.contentHeight) + } else { + useScrollView = viewModel.contentHeight > frame.height + Const.Size.ios15scrollOffset + } + + return useScrollView + } +} + +private enum Const { + enum Size { + static let closeButtonPadding: CGFloat = 16.0 + static let horizontalPadding: CGFloat = 48.0 + static let horizontalPaddingPortrait: CGFloat = 44.0 + static let horizontalPaddingPortraitSmallFrame: CGFloat = 16.0 + static let topPadding: CGFloat = 36.0 + static let headlineTopPadding: CGFloat = 24.0 + static let ios15scrollOffset: CGFloat = 80.0 + static let contentSpacerHeight: CGFloat = 44.0 + static let contentSpacerHeightLandscape: CGFloat = 50.0 + static let bottomPadding: CGFloat = 12.0 + static let bottomPaddingIPad: CGFloat = 24.0 + } +} + +private extension Image { + static let lock = Image("AutofillLock") +} + +#Preview { + AuthConfirmationPromptView(viewModel: AuthConfirmationPromptViewModel()) +} diff --git a/DuckDuckGo/AuthConfirmationPromptViewController.swift b/DuckDuckGo/AuthConfirmationPromptViewController.swift new file mode 100644 index 0000000000..fabcf3273c --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptViewController.swift @@ -0,0 +1,106 @@ +// +// AuthConfirmationPromptViewController.swift +// DuckDuckGo +// +// 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 UIKit +import SwiftUI + +final class AuthConfirmationPromptViewController: UIViewController { + + typealias AuthConfirmationCompletion = (_ authenticated: Bool) -> Void + + private let didBeginAuthenticating: () -> Void + private let authConfirmationCompletion: AuthConfirmationCompletion + + private var viewModel: AuthConfirmationPromptViewModel? + + init(didBeginAuthenticating: @escaping () -> Void, + authConfirmationCompletion: @escaping AuthConfirmationCompletion) { + self.didBeginAuthenticating = didBeginAuthenticating + self.authConfirmationCompletion = authConfirmationCompletion + + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + + view.backgroundColor = UIColor(named: "AutofillPromptLargeBackground") + + setupAuthConfirmationPromptView() + } + + private func setupAuthConfirmationPromptView() { + let authConfirmationPromptViewModel = AuthConfirmationPromptViewModel() + authConfirmationPromptViewModel.delegate = self + viewModel = authConfirmationPromptViewModel + + let authConfirmationPromptView = AuthConfirmationPromptView(viewModel: authConfirmationPromptViewModel) + let controller = UIHostingController(rootView: authConfirmationPromptView) + controller.view.backgroundColor = .clear + presentationController?.delegate = self + installChildViewController(controller) + } + +} + +// MARK: UISheetPresentationControllerDelegate + +extension AuthConfirmationPromptViewController: UISheetPresentationControllerDelegate { + + func presentationControllerDidDismiss(_ presentationController: UIPresentationController) { + authConfirmationCompletion(false) + } + +} + +// MARK: AuthConfirmationPromptViewModelDelegate + +extension AuthConfirmationPromptViewController: AuthConfirmationPromptViewModelDelegate { + + func authConfirmationPromptViewModelDidBeginAuthenticating(_ viewModel: AuthConfirmationPromptViewModel) { + didBeginAuthenticating() + } + + func authConfirmationPromptViewModelDidAuthenticate(_ viewModel: AuthConfirmationPromptViewModel, success: Bool) { + dismiss(animated: true) { + self.authConfirmationCompletion(success) + } + } + + func authConfirmationPromptViewModelDidCancel(_ viewModel: AuthConfirmationPromptViewModel) { + dismiss(animated: true) { + self.authConfirmationCompletion(false) + } + } + + func authConfirmationPromptViewModelDidResizeContent(_ viewModel: AuthConfirmationPromptViewModel, contentHeight: CGFloat) { + if #available(iOS 16.0, *) { + if let sheetPresentationController = self.presentationController as? UISheetPresentationController { + sheetPresentationController.animateChanges { + sheetPresentationController.detents = [.custom(resolver: { _ in contentHeight })] + } + } + } + } + +} diff --git a/DuckDuckGo/AuthConfirmationPromptViewModel.swift b/DuckDuckGo/AuthConfirmationPromptViewModel.swift new file mode 100644 index 0000000000..0ac7cadc50 --- /dev/null +++ b/DuckDuckGo/AuthConfirmationPromptViewModel.swift @@ -0,0 +1,60 @@ +// +// AuthConfirmationPromptViewModel.swift +// DuckDuckGo +// +// 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 AuthConfirmationPromptViewModelDelegate: AnyObject { + func authConfirmationPromptViewModelDidBeginAuthenticating(_ viewModel: AuthConfirmationPromptViewModel) + func authConfirmationPromptViewModelDidAuthenticate(_ viewModel: AuthConfirmationPromptViewModel, success: Bool) + func authConfirmationPromptViewModelDidCancel(_ viewModel: AuthConfirmationPromptViewModel) + func authConfirmationPromptViewModelDidResizeContent(_ viewModel: AuthConfirmationPromptViewModel, contentHeight: CGFloat) +} + +final class AuthConfirmationPromptViewModel: ObservableObject { + + weak var delegate: AuthConfirmationPromptViewModelDelegate? + private let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillDeleteAllPasswordsAuthenticationReason) + + var contentHeight: CGFloat = AutofillViews.deleteAllPromptMinHeight { + didSet { + guard contentHeight != oldValue else { + return + } + delegate?.authConfirmationPromptViewModelDidResizeContent(self, + contentHeight: max(contentHeight, AutofillViews.deleteAllPromptMinHeight)) + } + } + + func authenticatePressed() { + delegate?.authConfirmationPromptViewModelDidBeginAuthenticating(self) + + authenticator.authenticate { [weak self] error in + self?.authCompleted(with: error == nil) + } + } + + func cancelButtonPressed() { + delegate?.authConfirmationPromptViewModelDidCancel(self) + } + + private func authCompleted(with success: Bool) { + delegate?.authConfirmationPromptViewModelDidAuthenticate(self, success: success) + } + +} diff --git a/DuckDuckGo/AutofillLoginDetailsView.swift b/DuckDuckGo/AutofillLoginDetailsView.swift index 589d858324..ba9eee9654 100644 --- a/DuckDuckGo/AutofillLoginDetailsView.swift +++ b/DuckDuckGo/AutofillLoginDetailsView.swift @@ -291,8 +291,8 @@ struct AutofillLoginDetailsView: View { let deleteAction = ActionSheet.Button.destructive(Text(UserText.autofillLoginDetailsDeleteConfirmationButtonTitle)) { viewModel.delete() } - return ActionSheet(title: Text(UserText.autofillLoginDetailsDeleteConfirmationTitle), - message: nil, + return ActionSheet(title: Text(UserText.autofillDeleteAllPasswordsActionTitle(for: 1)), + message: Text(viewModel.deleteMessage()), buttons: [deleteAction, ActionSheet.Button.cancel()]) }) .foregroundColor(Color.red) diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index c44a48f94f..803e5d1cce 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -22,6 +22,7 @@ import SwiftUI import BrowserServicesKit import Common import Combine +import DDGSync protocol AutofillLoginDetailsViewControllerDelegate: AnyObject { func autofillLoginDetailsViewControllerDidSave(_ controller: AutofillLoginDetailsViewController, account: SecureVaultModels.WebsiteAccount?) @@ -69,8 +70,8 @@ class AutofillLoginDetailsViewController: UIViewController { constant: 144) }() - init(authenticator: AutofillLoginListAuthenticator, account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, authenticationNotRequired: Bool = false) { - self.viewModel = AutofillLoginDetailsViewModel(account: account, tld: tld) + init(authenticator: AutofillLoginListAuthenticator, syncService: DDGSyncing, account: SecureVaultModels.WebsiteAccount? = nil, tld: TLD, authenticationNotRequired: Bool = false) { + self.viewModel = AutofillLoginDetailsViewModel(account: account, syncService: syncService, tld: tld) self.authenticator = authenticator self.authenticationNotRequired = authenticationNotRequired super.init(nibName: nil, bundle: nil) diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 0e9ec04872..2c7ec10da8 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -26,6 +26,7 @@ import SwiftUI import Core import DesignResourcesKit import SecureStorage +import DDGSync protocol AutofillLoginDetailsViewModelDelegate: AnyObject { func autofillLoginDetailsViewModelDidSave() @@ -63,6 +64,7 @@ final class AutofillLoginDetailsViewModel: ObservableObject { weak var delegate: AutofillLoginDetailsViewModelDelegate? var account: SecureVaultModels.WebsiteAccount? var emailManager: EmailManager + private let syncService: DDGSyncing private let tld: TLD private let autofillDomainNameUrlMatcher = AutofillDomainNameUrlMatcher() @@ -178,9 +180,11 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } internal init(account: SecureVaultModels.WebsiteAccount? = nil, + syncService: DDGSyncing, tld: TLD, emailManager: EmailManager = EmailManager()) { self.account = account + self.syncService = syncService self.tld = tld self.headerViewModel = AutofillLoginDetailsHeaderViewModel() self.emailManager = emailManager @@ -228,7 +232,15 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } } } - + + func deleteMessage() -> String { + if syncService.authState == .inactive { + return UserText.autofillDeleteAllPasswordsActionMessage(for: 1) + } else { + return UserText.autofillDeleteAllPasswordsSyncActionMessage(for: 1) + } + } + func copyToPasteboard(_ action: PasteboardCopyAction) { var message = "" switch action { diff --git a/DuckDuckGo/AutofillLoginListViewModel.swift b/DuckDuckGo/AutofillLoginListViewModel.swift index 20abb86e80..bead3e9829 100644 --- a/DuckDuckGo/AutofillLoginListViewModel.swift +++ b/DuckDuckGo/AutofillLoginListViewModel.swift @@ -50,6 +50,7 @@ internal enum EnableAutofillRows: Int, CaseIterable { case resetNeverPromptWebsites } +// swiftlint:disable file_length type_body_length final class AutofillLoginListViewModel: ObservableObject { enum ViewState { @@ -63,8 +64,13 @@ final class AutofillLoginListViewModel: ObservableObject { let authenticator = AutofillLoginListAuthenticator(reason: UserText.autofillLoginListAuthenticationReason) var isSearching: Bool = false + var isEditing: Bool = false { + didSet { + sections = makeSections(with: accounts) + } + } var authenticationNotRequired = false - private var accounts = [SecureVaultModels.WebsiteAccount]() + @Published private var accounts = [SecureVaultModels.WebsiteAccount]() private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]() private var cancellables: Set = [] private var appSettings: AppSettings @@ -87,6 +93,16 @@ final class AutofillLoginListViewModel: ObservableObject { var hasAccountsSaved: Bool { return !accounts.isEmpty } + + var accountsCount: Int { + accounts.count + } + + var accountsCountPublisher: AnyPublisher { + $accounts + .map { $0.count } + .eraseToAnyPublisher() + } var isAutofillEnabledInSettings: Bool { get { appSettings.autofillCredentialsEnabled } @@ -124,6 +140,10 @@ final class AutofillLoginListViewModel: ObservableObject { return false } + func deleteAllCredentials() -> Bool { + return deleteAll() + } + func undoLastDelete() { guard let cachedDeletedCredentials = cachedDeletedCredentials else { return @@ -134,7 +154,17 @@ final class AutofillLoginListViewModel: ObservableObject { func clearUndoCache() { cachedDeletedCredentials = nil } - + + func clearAllAccounts() { + accounts = [] + accountsToSuggest = [] + sections = makeSections(with: accounts) + } + + func undoClearAllAccounts() { + updateData() + } + func lockUI() { authenticationNotRequired = !hasAccountsSaved authenticator.logOut() @@ -233,7 +263,9 @@ final class AutofillLoginListViewModel: ObservableObject { var newSections = [AutofillLoginListSectionType]() if !isSearching { - newSections.append(.enableAutofill) + if !isEditing { + newSections.append(.enableAutofill) + } if !accountsToSuggest.isEmpty { let accountItems = accountsToSuggest.map { AutofillLoginListItemViewModel(account: $0, @@ -278,6 +310,8 @@ final class AutofillLoginListViewModel: ObservableObject { } else { newViewState = .searching } + } else if isEditing { + newViewState = sections.count >= 1 ? .showItems : .empty } else { newViewState = sections.count > 1 ? .showItems : .empty } @@ -338,7 +372,21 @@ final class AutofillLoginListViewModel: ObservableObject { Pixel.fire(pixel: .secureVaultError, error: error) } } + + @discardableResult + private func deleteAll() -> Bool { + guard let secureVault = secureVault else { return false } + + do { + try secureVault.deleteAllWebsiteCredentials() + return true + } catch { + Pixel.fire(pixel: .secureVaultError, error: error) + return false + } + } } +// swiftlint:enable type_body_length extension AutofillLoginListItemViewModel: Comparable { static func < (lhs: AutofillLoginListItemViewModel, rhs: AutofillLoginListItemViewModel) -> Bool { diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index c3b6e26c05..7b90ad5868 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -55,7 +55,34 @@ final class AutofillLoginSettingsListViewController: UIViewController { target: self, action: #selector(addButtonPressed)) }() - + + private lazy var deleteAllButtonItem: UIBarButtonItem = { + let button = UIBarButtonItem(title: UserText.autofillLoginListToolbarDeleteAllButton, + style: .plain, + target: self, + action: #selector(deleteAll)) + button.tintColor = .systemRed + return button + }() + + private lazy var accountsCountLabel: UILabel = { + let label = UILabel() + label.font = .daxCaption() + label.textColor = UIColor(designSystemColor: .textSecondary) + label.text = UserText.autofillLoginListToolbarPasswordsCount(viewModel.accountsCount) + return label + }() + + private lazy var accountsCountButtonItem: UIBarButtonItem = { + let item = UIBarButtonItem(customView: accountsCountLabel) + return item + }() + + private lazy var flexibleSpace: UIBarButtonItem = { + let space = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + return space + }() + private var cancellables: Set = [] private lazy var searchController: UISearchController = { let searchController = UISearchController(searchResultsController: nil) @@ -78,8 +105,6 @@ final class AutofillLoginSettingsListViewController: UIViewController { tableView.registerCell(ofType: AutofillListItemTableViewCell.self) tableView.registerCell(ofType: EnableAutofillSettingsTableViewCell.self) tableView.registerCell(ofType: AutofillNeverSavedTableViewCell.self) - // Have to set tableHeaderView height otherwise tableView content will jump when adding / removing searchController due to tableView insetGrouped style - tableView.tableHeaderView = UIView(frame: CGRect(x: 0, y: 0, width: 0, height: 24)) return tableView }() @@ -102,7 +127,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { multiplier: 1, constant: (tableView.frame.height / 2)) }() - + var selectedAccount: SecureVaultModels.WebsiteAccount? init(appSettings: AppSettings, currentTabUrl: URL? = nil, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, selectedAccount: SecureVaultModels.WebsiteAccount?) { @@ -156,6 +181,9 @@ final class AutofillLoginSettingsListViewController: UIViewController { override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) + if isMovingFromParent { + navigationController?.isToolbarHidden = true + } if viewModel.authenticator.canAuthenticate() && viewModel.authenticator.state == .loggedIn { AppDependencyProvider.shared.autofillLoginSession.startSession() } @@ -192,13 +220,19 @@ final class AutofillLoginSettingsListViewController: UIViewController { tableView.setEditing(editing, animated: animated) + // trigger re-build of table sections + viewModel.isEditing = editing + tableView.reloadData() + updateNavigationBarButtons() updateSearchController() + updateToolbar() } - + @objc func addButtonPressed() { let detailsController = AutofillLoginDetailsViewController(authenticator: viewModel.authenticator, + syncService: syncService, tld: tld, authenticationNotRequired: viewModel.authenticationNotRequired) detailsController.delegate = self @@ -209,6 +243,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { func makeAccountDetailsScreen(_ account: SecureVaultModels.WebsiteAccount) -> AutofillLoginDetailsViewController { let detailsController = AutofillLoginDetailsViewController(authenticator: viewModel.authenticator, + syncService: syncService, account: account, tld: tld, authenticationNotRequired: viewModel.authenticationNotRequired) @@ -253,24 +288,31 @@ final class AutofillLoginSettingsListViewController: UIViewController { self?.tableView.reloadData() } .store(in: &cancellables) + + viewModel.accountsCountPublisher + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateToolbarLabel() + } + .store(in: &cancellables) + } private func configureNotification() { - let notificationCenter = NotificationCenter.default - notificationCenter.addObserver(self, - selector: #selector(appWillMoveToForegroundCallback), - name: UIApplication.willEnterForegroundNotification, object: nil) - - notificationCenter.addObserver(self, - selector: #selector(appWillMoveToBackgroundCallback), - name: UIApplication.willResignActiveNotification, object: nil) + addObserver(for: UIApplication.didBecomeActiveNotification, selector: #selector(appDidBecomeActiveCallback)) + addObserver(for: UIApplication.willResignActiveNotification, selector: #selector(appWillResignActiveCallback)) + addObserver(for: AutofillLoginListAuthenticator.Notifications.invalidateContext, selector: #selector(authenticatorInvalidateContext)) + } - notificationCenter.addObserver(self, - selector: #selector(authenticatorInvalidateContext), - name: AutofillLoginListAuthenticator.Notifications.invalidateContext, object: nil) + private func addObserver(for notification: Notification.Name, selector: Selector) { + NotificationCenter.default.addObserver(self, selector: selector, name: notification, object: nil) } - - @objc private func appWillMoveToForegroundCallback() { + + private func removeObserver(for notification: Notification.Name) { + NotificationCenter.default.removeObserver(self, name: notification, object: nil) + } + + @objc private func appDidBecomeActiveCallback() { // AutofillLoginDetailsViewController will handle calling authenticate() if it is the top view controller guard navigationController?.topViewController is AutofillLoginDetailsViewController else { authenticate() @@ -278,7 +320,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } - @objc private func appWillMoveToBackgroundCallback() { + @objc private func appWillResignActiveCallback() { viewModel.lockUI() } @@ -324,7 +366,83 @@ final class AutofillLoginSettingsListViewController: UIViewController { userInfo: [FireproofFaviconUpdater.UserInfoKeys.faviconDomain: domain]) }) } - + + @objc private func deleteAll() { + let message = self.syncService.authState == .inactive ? UserText.autofillDeleteAllPasswordsActionMessage(for: viewModel.accountsCount) + : UserText.autofillDeleteAllPasswordsSyncActionMessage(for: viewModel.accountsCount) + let alert = UIAlertController(title: UserText.autofillDeleteAllPasswordsActionTitle(for: viewModel.accountsCount), + message: message, + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: UserText.actionCancel, style: .cancel)) + let deleteAllAction = UIAlertAction(title: UserText.actionDelete, style: .destructive) {[weak self] _ in + self?.presentAuthConfirmationPrompt() + } + alert.addAction(deleteAllAction) + alert.preferredAction = deleteAllAction + present(controller: alert, fromView: tableView) + } + + private func presentAuthConfirmationPrompt() { + let authConfirmationPromptViewController = AuthConfirmationPromptViewController( + didBeginAuthenticating: { [weak self] in + self?.configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: true) + }, authConfirmationCompletion: { [weak self] authenticated in + self?.configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: false) + + if authenticated { + let accountsCount = self?.viewModel.accountsCount ?? 0 + self?.viewModel.clearAllAccounts() + self?.presentDeleteAllConfirmation(accountsCount) + } + } + ) + + if #available(iOS 15.0, *) { + if let presentationController = authConfirmationPromptViewController.presentationController as? UISheetPresentationController { + if #available(iOS 16.0, *) { + presentationController.detents = [.custom(resolver: { _ in + AutofillViews.deleteAllPromptMinHeight + })] + } else { + presentationController.detents = [.medium()] + } + } + } + + present(authConfirmationPromptViewController, animated: true) + } + + private func configureObserversBasedOnAuthConfirmationPrompt(isAuthenticating: Bool) { + if isAuthenticating { + addObserver(for: UIApplication.didEnterBackgroundNotification, selector: #selector(appWillResignActiveCallback)) + removeObserver(for: UIApplication.willResignActiveNotification) + } else { + addObserver(for: UIApplication.willResignActiveNotification, selector: #selector(appWillResignActiveCallback)) + removeObserver(for: UIApplication.didEnterBackgroundNotification) + } + } + + private func presentDeleteAllConfirmation(_ numberOfAccounts: Int) { + var shouldDeleteAccounts = true + + ActionMessageView.present(message: UserText.autofillAllPasswordsDeletedToastMessage(for: numberOfAccounts), + actionTitle: UserText.actionGenericUndo, + presentationLocation: .withoutBottomBar, + onAction: { + shouldDeleteAccounts = false + }, onDidDismiss: { + if shouldDeleteAccounts { + if self.viewModel.deleteAllCredentials() { + self.syncService.scheduler.notifyDataChanged() + self.viewModel.resetNeverPromptWebsites() + self.viewModel.updateData() + } + } else { + self.viewModel.undoClearAllAccounts() + } + }) + } + // MARK: Subviews Setup private func updateViewState() { @@ -370,6 +488,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } updateNavigationBarButtons() updateSearchController() + updateToolbar() tableView.reloadData() } @@ -424,6 +543,24 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } + private func updateToolbar() { + if tableView.isEditing && viewModel.viewState == .showItems { + updateToolbarLabel() + navigationController?.isToolbarHidden = false + toolbarItems = [deleteAllButtonItem, flexibleSpace, accountsCountButtonItem, flexibleSpace] + } else { + toolbarItems?.removeAll() + navigationController?.isToolbarHidden = true + } + } + + private func updateToolbarLabel() { + guard tableView.isEditing else { return } + + accountsCountLabel.text = UserText.autofillLoginListToolbarPasswordsCount(viewModel.accountsCount) + accountsCountLabel.sizeToFit() + } + private func installSubviews() { view.addSubview(tableView) tableView.addSubview(emptySearchView) diff --git a/DuckDuckGo/AutofillViews.swift b/DuckDuckGo/AutofillViews.swift index aae551c98f..633d2fe261 100644 --- a/DuckDuckGo/AutofillViews.swift +++ b/DuckDuckGo/AutofillViews.swift @@ -29,6 +29,7 @@ struct AutofillViews { static let updateUsernameMinHeight = 310.0 static let passwordGenerationMinHeight: CGFloat = 310.0 static let emailSignupPromptMinHeight: CGFloat = 260.0 + static let deleteAllPromptMinHeight: CGFloat = 360.0 struct CloseButtonHeader: View { let action: () -> Void diff --git a/DuckDuckGo/PasswordGenerationPromptView.swift b/DuckDuckGo/PasswordGenerationPromptView.swift index 470084db8f..8c01fdabf6 100644 --- a/DuckDuckGo/PasswordGenerationPromptView.swift +++ b/DuckDuckGo/PasswordGenerationPromptView.swift @@ -41,32 +41,32 @@ struct PasswordGenerationPromptView: View { .offset(x: horizontalPadding) .zIndex(1) - VStack { - Spacer() - .frame(height: Const.Size.topPadding) - AutofillViews.AppIconHeader() - Spacer() - .frame(height: Const.Size.headlineTopPadding) - AutofillViews.Headline(title: UserText.autofillPasswordGenerationPromptTitle) - if #available(iOS 16.0, *) { - passwordView - .padding([.top, .bottom], passwordVerticalPadding) - } else { - AutofillViews.LegacySpacerView() - passwordView - AutofillViews.LegacySpacerView() - } - AutofillViews.Description(text: UserText.autofillPasswordGenerationPromptSubtitle) - contentViewSpacer - ctaView - .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad - : Const.Size.bottomPadding) + VStack { + Spacer() + .frame(height: Const.Size.topPadding) + AutofillViews.AppIconHeader() + Spacer() + .frame(height: Const.Size.headlineTopPadding) + AutofillViews.Headline(title: UserText.autofillPasswordGenerationPromptTitle) + if #available(iOS 16.0, *) { + passwordView + .padding([.top, .bottom], passwordVerticalPadding) + } else { + AutofillViews.LegacySpacerView() + passwordView + AutofillViews.LegacySpacerView() } - .background(GeometryReader { proxy -> Color in - DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } - return Color.clear - }) - .useScrollView(shouldUseScrollView(), minHeight: frame.height) + AutofillViews.Description(text: UserText.autofillPasswordGenerationPromptSubtitle) + contentViewSpacer + ctaView + .padding(.bottom, AutofillViews.isIPad(verticalSizeClass, horizontalSizeClass) ? Const.Size.bottomPaddingIPad + : Const.Size.bottomPadding) + } + .background(GeometryReader { proxy -> Color in + DispatchQueue.main.async { viewModel.contentHeight = proxy.size.height } + return Color.clear + }) + .useScrollView(shouldUseScrollView(), minHeight: frame.height) } .padding(.horizontal, horizontalPadding) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index ca0a7e8886..8a769e5c59 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -728,6 +728,32 @@ But if you *do* want a peek under the hood, you can find more information about public static let autofillResetNeverSavedActionConfirmButton = NSLocalizedString("autofill.logins.list.never.saved.reset.action.confirm", value: "Reset Excluded Sites", comment: "Confirm button to reset list of never saved sites") public static let autofillResetNeverSavedActionCancelButton = NSLocalizedString("autofill.logins.list.never.saved.reset.action.cancel", value: "Cancel", comment: "Cancel button for resetting list of never saved sites") + public static let autofillLoginListToolbarDeleteAllButton = NSLocalizedString("autofill.logins.list.delete.all", value:"Delete All", comment: "Title for button to delete all saved autofill passwords") + public static func autofillLoginListToolbarPasswordsCount(_ count: Int) -> String { + let message = NSLocalizedString("autofill.number.of.passwords", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count) + } + public static func autofillDeleteAllPasswordsActionTitle(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.confirmation.title", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count) + } + public static func autofillDeleteAllPasswordsActionMessage(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.confirmation.body", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count, count) + } + public static func autofillDeleteAllPasswordsSyncActionMessage(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.sync.confirmation.body", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count, count) + } + + public static let autofillDeleteAllPasswordsAuthenticationPromptTitle = NSLocalizedString("autofill.logins.delete.all.authentication.prompt.title", value: "Authenticate To Delete All Passwords", comment: "Title of prompt requiring authentication before all passwords are deleted") + public static let autofillDeleteAllPasswordsAuthenticationPromptButton = NSLocalizedString("autofill.logins.delete.all.authentication.prompt.button", value: "Authenticate Now", comment: "Title of button in prompt requiring authentication before all passwords are deleted") + public static let autofillDeleteAllPasswordsAuthenticationReason = NSLocalizedString("autofill.logins.delete.all.authentication.reason", value:"Authenticate to confirm you want to delete all passwords", comment: "Reason for authentication when deleting all logins") + public static func autofillAllPasswordsDeletedToastMessage(for count: Int) -> String { + let message = NSLocalizedString("autofill.delete.all.passwords.completion", comment: "Do not translate - stringsdict entry") + return message.format(arguments: count) + } + public static let autofillLoginPromptAuthenticationCancelButton = NSLocalizedString("autofill.logins.prompt.auth.cancel", value:"Cancel", comment: "Cancel button for auth during login prompt") public static let autofillLoginPromptAuthenticationReason = NSLocalizedString("autofill.logins.prompt.auth.reason", value:"Unlock to use saved password", comment: "Reason for auth during login prompt") public static let autofillLoginPromptTitle = NSLocalizedString("autofill.logins.prompt.title", value:"Use a saved password?", comment: "Title for autofill login prompt") @@ -766,7 +792,6 @@ But if you *do* want a peek under the hood, you can find more information about public static let autofillLoginDetailsEditTitle = NSLocalizedString("autofill.logins.details.edit-title", value:"Edit Password", comment: "Title when editing autofill login details") public static let autofillLoginDetailsNewTitle = NSLocalizedString("autofill.logins.details.new-title", value:"Add password", comment: "Title when adding new autofill login") public static let autofillLoginDetailsDeleteButton = NSLocalizedString("autofill.logins.details.delete", value:"Delete Password", comment: "Delete button when deleting an autofill login") - public static let autofillLoginDetailsDeleteConfirmationTitle = NSLocalizedString("autofill.logins.details.delete-confirmation.title", value:"Are you sure you want to delete this password?", comment: "Title of confirmation alert when deleting an autofill login") public static let autofillLoginDetailsDeleteConfirmationButtonTitle = NSLocalizedString("autofill.logins.details.delete-confirmation.button", value:"Delete Password", comment: "Autofill alert button confirming delete autofill login") public static func autofillLoginListLoginDeletedToastMessage(for title: String) -> String { diff --git a/DuckDuckGo/bg.lproj/Localizable.strings b/DuckDuckGo/bg.lproj/Localizable.strings index 4813d1983a..52ec7e2e9d 100644 --- a/DuckDuckGo/bg.lproj/Localizable.strings +++ b/DuckDuckGo/bg.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL адресът е копиран"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Изтриване"; /* Disable protection action */ "action.title.disable.protection" = "Деактивиране на защита на поверителността"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Когато DuckDuckGo открие изскачащи прозорци за съгласие за използване на бисквитки в сайтовете, които посещавате, можем да опитаме автоматично да настроим предпочитанията Ви за бисквитки, така че бисквитките да са сведени до минимум, а поверителността да е максимална, след което да затворим изскачащите прозорци. Някои сайтове не предоставят възможност за управление на предпочитанията за бисквитки, затова можем само да скрием техните изскачащи прозорци."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Активиране на Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Потребителското име е копирано"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Удостоверяване сега"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Удостоверете се, за да изтриете всички пароли"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Удостоверете се, за да потвърдите, че искате да изтриете всички пароли"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Адрес на уебсайта"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Изтриване на парола"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Сигурни ли сте, че искате да изтриете тази парола?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Редактиране на парола"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Затваряне"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Изтриване на всички"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Запазване и автоматично попълване на паролите"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Няма резултати"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Паролите се съхраняват сигурно на Вашето устройство."; diff --git a/DuckDuckGo/bg.lproj/Localizable.stringsdict b/DuckDuckGo/bg.lproj/Localizable.stringsdict index 31ab40f11b..b39ecd6dc3 100644 --- a/DuckDuckGo/bg.lproj/Localizable.stringsdict +++ b/DuckDuckGo/bg.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d парола + other + %1$d пароли + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Сигурни ли сте, че искате да изтриете %1$d парола? + other + Сигурни ли сте, че искате да изтриете %1$d пароли? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Вашите пароли ще бъдат изтрити от това устройство. Уверете се, че все още имате друг начин за достъп до Вашия %2$#@accounts@. + other + Вашите пароли ще бъдат изтрити от това устройство. Уверете се, че все още имате друг начин за достъп до Вашите %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + акаунт + other + акаунти + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Вашите пароли ще бъдат изтрити от всички синхронизирани устройства. Уверете се, че все още имате друг начин за достъп до Вашия %2$#@accounts@. + other + Вашите пароли ще бъдат изтрити от всички синхронизирани устройства. Уверете се, че все още имате друг начин за достъп до Вашите %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + акаунт + other + акаунти + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d изтрита парола + other + %1$d изтрити пароли + + diff --git a/DuckDuckGo/cs.lproj/Localizable.strings b/DuckDuckGo/cs.lproj/Localizable.strings index c3b5ed0be1..876f184fb9 100644 --- a/DuckDuckGo/cs.lproj/Localizable.strings +++ b/DuckDuckGo/cs.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Adrese URL zkopírována"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Smazat"; /* Disable protection action */ "action.title.disable.protection" = "Zakázat ochranu soukromí"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Když DuckDuckGo na navštívené stránce zjistí vyskakovací okno žádající o souhlas se soubory cookie, můžeme zkusit automaticky nastavit tvou předvolbu na co nejméně souborů cookie a maximální ochranu soukromí a potom vyskakovací okno zavřít. Některé weby nenabízejí možnost si nastavit předvolby souborů cookie, takže podobná vyskakovací okna můžeme jen skrýt."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Povol funkci Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Uživatelské jméno zkopírované"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Ověřit"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Pro smazání všech hesel proveď ověření"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Ověřením potvrď, že chceš smazat všechna hesla"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL webové stránky"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Smazat heslo"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Opravdu chceš smazat tohle heslo?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Upravit heslo"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zavřít"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Smazat vše"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Ukládání a automatické vyplňování hesel"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Žádné výsledky"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Hesla se bezpečně ukládají do tvého zařízení."; diff --git a/DuckDuckGo/cs.lproj/Localizable.stringsdict b/DuckDuckGo/cs.lproj/Localizable.stringsdict index 5d4152ff8d..001afa04dd 100644 --- a/DuckDuckGo/cs.lproj/Localizable.stringsdict +++ b/DuckDuckGo/cs.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Zablokovali jsme je. d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d heslo + few + %1$d hesla + many + %1$d hesla + other + %1$d hesel + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Opravdu chceš smazat %1$d heslo? + few + Opravdu chceš smazat %1$d hesla? + many + Opravdu chceš smazat %1$d hesla? + other + Opravdu chceš smazat %1$d hesel? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoje heslo se z tohohle zařízení smaže. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + few + Tvoje hesla se z tohohle zařízení smažou. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + many + Tvoje hesla se z tohohle zařízení smažou. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + other + Tvoje hesla se z tohohle zařízení smažou. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účtu + few + účtům + many + účtům + other + účtům + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoje heslo se smaže ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + few + Tvoje hesla se smažou ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + many + Tvoje hesla se smažou ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + other + Tvoje hesla se smažou ze všech synchronizovaných zařízení. Zkontroluj si předtím, že se k %2$#@accounts@ i tak dostaneš. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účtu + few + účtům + many + účtům + other + účtům + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d smazané heslo + few + %1$d smazaná hesla + many + %1$d smazaného hesla + other + %1$d smazaných hesel + + diff --git a/DuckDuckGo/da.lproj/Localizable.strings b/DuckDuckGo/da.lproj/Localizable.strings index 5175783178..b9b4c0fdcf 100644 --- a/DuckDuckGo/da.lproj/Localizable.strings +++ b/DuckDuckGo/da.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL kopieret"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Slet"; /* Disable protection action */ "action.title.disable.protection" = "Deaktiver Beskyttelse af privatlivet"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Når DuckDuckGo registrerer pop op-vinduer med samtykke til brug af cookies på websteder, du besøger, kan vi automatisk indstille dine cookiepræferencer til at minimere cookies og maksimere privatlivets fred og derefter lukke pop op-vinduer. Nogle websteder giver ikke mulighed for at administrere cookiepræferencer, så vi kan kun skjule pop op-vinduer som disse."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Aktivér Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Brugernavn kopieret"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Godkend nu"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Godkend for at slette alle adgangskoder"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Bekræft, at du vil slette alle adgangskoder"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL til webstedet"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Slet adgangskode"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Er du sikker på, at du vil slette denne adgangskode?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Rediger adgangskode"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Luk"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Slet alle"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Gem og udfyld adgangskoder automatisk"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ingen resultater"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Adgangskoder gemmes sikkert på din enhed."; diff --git a/DuckDuckGo/da.lproj/Localizable.stringsdict b/DuckDuckGo/da.lproj/Localizable.stringsdict index f7962120bd..e2ddc6808d 100644 --- a/DuckDuckGo/da.lproj/Localizable.stringsdict +++ b/DuckDuckGo/da.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Jeg blokerede dem! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d adgangskode + other + %1$d adgangskoder + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Er du sikker på, at du vil slette %1$d adgangskode? + other + Er du sikker på, at du vil slette %1$d adgangskoder? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Din adgangskode slettes fra denne enhed. Sørg for, at du stadig har en måde at få adgang til din %2$#@accounts@. + other + Dine adgangskoder slettes fra denne enhed. Sørg for, at du stadig har en måde at få adgang til dine %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konti + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Din adgangskode slettes fra alle synkroniserede enheder. Husk at sikre, at du stadig har adgang til dine konti.%2$#@accounts@ + other + Dine adgangskoder slettes fra alle synkroniserede enheder. Husk at sikre, at du stadig har adgang til dine konti.%2$#@accounts@ + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konti + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d adgangskode slettet + other + %1$d adgangskoder slettet + + diff --git a/DuckDuckGo/de.lproj/Localizable.strings b/DuckDuckGo/de.lproj/Localizable.strings index 6dd24b2bb8..f8fe8a095c 100644 --- a/DuckDuckGo/de.lproj/Localizable.strings +++ b/DuckDuckGo/de.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL wurde kopiert"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Löschen"; /* Disable protection action */ "action.title.disable.protection" = "Datenschutz deaktivieren"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Wenn DuckDuckGo Pop-ups zur Cookie-Zustimmung auf den von dir besuchten Websites erkennt, können wir deine Cookie-Einstellungen automatisch so festlegen, dass Cookies minimiert werden und der Datenschutz maximiert wird, und dann die Pop-ups schließen. Einige Websites bieten keine Option zur Verwaltung von Cookie-Einstellungen, sodass wir Pop-ups wie diese nur ausblenden können."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Email Protection aktivieren"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Benutzername kopiert"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Jetzt authentifizieren"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Authentifiziere dich, um alle Passwörter zu löschen"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Authentifiziere dich, um zu bestätigen, dass du alle Passwörter löschen willst"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL der Website"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Passwort löschen"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Möchtest du dieses Passwort wirklich löschen?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Passwort bearbeiten"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Schließen"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Alle löschen"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Passwörter speichern und automatisch ausfüllen"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Keine Ergebnisse"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Passwörter werden sicher auf deinem Gerät gespeichert."; diff --git a/DuckDuckGo/de.lproj/Localizable.stringsdict b/DuckDuckGo/de.lproj/Localizable.stringsdict index cb2af7bb99..110ed364d5 100644 --- a/DuckDuckGo/de.lproj/Localizable.stringsdict +++ b/DuckDuckGo/de.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Ich habe sie blockiert! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d Passwort + other + %1$d Passwörter + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Möchtest du dieses %1$d Passwort wirklich löschen? + other + Möchtest du diese %1$d Passwörter wirklich löschen? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Dein Passwort wird von diesem Gerät gelöscht. Stelle sicher, dass du immer noch eine Möglichkeit hast, auf dein %2$#@accounts@ zuzugreifen. + other + Deine Passwörter werden von diesem Gerät gelöscht. Stelle sicher, dass du immer noch eine Möglichkeit hast, auf deine %2$#@accounts@ zuzugreifen. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Konto + other + Konten + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Dein Passwort wird von allen synchronisierten Geräten gelöscht. Vergewissere dich, dass du weiterhin eine Möglichkeit hast, auf deine %2$#@accounts@ zuzugreifen. + other + Deine Passwörter werden von allen synchronisierten Geräten gelöscht. Vergewissere dich, dass du weiterhin eine Möglichkeit hast, auf deine %2$#@accounts@ zuzugreifen. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Konto + other + Konten + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d Passwort gelöscht + other + %1$d Passwörter gelöscht + + diff --git a/DuckDuckGo/el.lproj/Localizable.strings b/DuckDuckGo/el.lproj/Localizable.strings index 1b49494fe1..50ae42fd35 100644 --- a/DuckDuckGo/el.lproj/Localizable.strings +++ b/DuckDuckGo/el.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Η διεύθυνση URL αντιγράφτηκε"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Διαγραφή"; /* Disable protection action */ "action.title.disable.protection" = "Απενεργοποίηση Προστασίας προσωπικών δεδομένων"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Όταν το DuckDuckGo εντοπίζει αναδυόμενα παράθυρα συναίνεσης για cookies σε ιστότοπους που επισκέπτεστε, μπορούμε να προσπαθήσουμε να ρυθμίσουμε αυτόματα τις προτιμήσεις σας για τα cookies ώστε να ελαχιστοποιήσετε τα cookies και να μεγιστοποιήσετε το απόρρητο. Έπειτα μπορείτε να κλείσετε τα αναδυόμενα παράθυρα. Ορισμένοι ιστότοποι δεν παρέχουν επιλογή για διαχείριση των προτιμήσεων cookies, οπότε μπορούμε μόνο να αποκρύψουμε αναδυόμενα παράθυρα όπως αυτά."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Ενεργοποίηση Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Το όνομα χρήστη αντιγράφηκε"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Έλεγχος ταυτότητας τώρα"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Έλεγχος ταυτότητας για διαγραφή όλων των κωδικών πρόσβασης"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Επαληθεύστε την ταυτότητά σας για να επιβεβαιώσετε ότι θέλετε να διαγράψετε όλους τους κωδικούς πρόσβασης"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Διεύθυνση URL ιστότοπου"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Διαγραφή κωδικού πρόσβασης"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Θέλετε σίγουρα να διαγράψετε αυτόν τον κωδικό πρόσβασης;"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Επεξεργασία κωδικού πρόσβασης"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Κλείσιμο"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Διαγραφή όλων"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Αποθήκευση και αυτόματη συμπλήρωση κωδικών πρόσβασης"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Κανένα αποτέλεσμα"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Οι κωδικοί πρόσβασης αποθηκεύονται με ασφάλεια στη συσκευή σας."; diff --git a/DuckDuckGo/el.lproj/Localizable.stringsdict b/DuckDuckGo/el.lproj/Localizable.stringsdict index 5483feb8f1..1bf4eaf174 100644 --- a/DuckDuckGo/el.lproj/Localizable.stringsdict +++ b/DuckDuckGo/el.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d κωδικός πρόσβασης + other + %1$d κωδικοί πρόσβασης + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Θέλετε σίγουρα να διαγράψετε %1$d κωδικούς πρόσβασης; + other + Θέλετε σίγουρα να διαγράψετε %1$d κωδικούς πρόσβασης; + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ο κωδικός πρόσβασής σας θα διαγραφεί από αυτήν τη συσκευή. Βεβαιωθείτε ότι εξακολουθείτε να έχετε έναν τρόπο πρόσβασης στο %2$#@accounts@. + other + Οι κωδικοί πρόσβασής σας θα διαγραφούν από αυτή τη συσκευή. Βεβαιωθείτε ότι εξακολουθείτε να έχετε έναν τρόπο πρόσβασης στο %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + λογαριασμό + other + λογαριασμούς + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ο κωδικός πρόσβασής σας θα διαγραφεί από όλες τις συγχρονισμένες συσκευές. Βεβαιωθείτε ότι έχετε ακόμα τρόπο πρόσβασης στους %2$#@accounts@ σας. + other + Οι κωδικοί πρόσβασής σας θα διαγραφούν από όλες τις συγχρονισμένες συσκευές. Βεβαιωθείτε ότι έχετε ακόμα τρόπο πρόσβασης στους %2$#@accounts@ σας. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + λογαριασμό + other + λογαριασμούς + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d κωδικός πρόσβασης διαγράφηκε + other + %1$d κωδικοί πρόσβασης διαγράφηκαν + + diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index cc817d9c34..4459952b4f 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "When DuckDuckGo detects cookie consent pop-ups on sites you visit, we can try to automatically set your cookie preferences to minimize cookies and maximize privacy, then close the pop-ups. Some sites don't provide an option to manage cookie preferences, so we can only hide pop-ups like these."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Enable Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Username copied"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Authenticate Now"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Authenticate To Delete All Passwords"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Authenticate to confirm you want to delete all passwords"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Website URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Delete Password"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Are you sure you want to delete this password?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Edit Password"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Close"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Delete All"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Save and autofill passwords"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "No Results"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Passwords are stored securely on your device."; diff --git a/DuckDuckGo/en.lproj/Localizable.stringsdict b/DuckDuckGo/en.lproj/Localizable.stringsdict index 695e58187d..df96547b47 100644 --- a/DuckDuckGo/en.lproj/Localizable.stringsdict +++ b/DuckDuckGo/en.lproj/Localizable.stringsdict @@ -194,5 +194,107 @@ I blocked them! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d passwords + one + %d password + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Are you sure you want to delete %d passwords? + one + Are you sure you want to delete this password? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Your passwords will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. + one + Your password will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + accounts + one + account + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Your passwords will be deleted from all synced devices. Make sure you still have a way to access your %2$#@accounts@. + one + Your password will be deleted from all synced devices. Make sure you still have a way to access your %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + accounts + one + account + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + %d passwords deleted + one + %d password deleted + + diff --git a/DuckDuckGo/es.lproj/Localizable.strings b/DuckDuckGo/es.lproj/Localizable.strings index 3d79ed55c8..bd06ea81a3 100644 --- a/DuckDuckGo/es.lproj/Localizable.strings +++ b/DuckDuckGo/es.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiada"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Eliminar"; /* Disable protection action */ "action.title.disable.protection" = "Desactivar la protección de privacidad"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Cuando DuckDuckGo detecta ventanas emergentes de consentimiento de cookies en los sitios que visitas, podemos configurar automáticamente tus preferencias de cookies para minimizar las cookies y maximizar la privacidad y, a continuación, cerrar las ventanas emergentes. Algunos sitios no ofrecen la opción de administrar las preferencias de cookies, por lo que solo podemos ocultar ventanas emergentes de este tipo."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Activar Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nombre de usuario copiado"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autenticar ahora"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autenticar para borrar todas las contraseñas"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentícate para confirmar que deseas borrar todas las contraseñas"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL del sitio web"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Eliminar contraseña"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "¿Seguro de que quieres borrar esta contraseña?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Editar contraseña"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Cerrar"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Eliminar todo"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Guardar y autocompletar contraseñas"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Sin resultados"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Las contraseñas se almacenan de forma segura en tu dispositivo."; diff --git a/DuckDuckGo/es.lproj/Localizable.stringsdict b/DuckDuckGo/es.lproj/Localizable.stringsdict index 9f7c52c3d0..48951c37f0 100644 --- a/DuckDuckGo/es.lproj/Localizable.stringsdict +++ b/DuckDuckGo/es.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d contraseña + other + %1$d contraseñas + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + ¿Seguro de que quieres borrar %1$d contraseña? + other + ¿Seguro de que quieres borrar %1$d contraseñas? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tu contraseña se eliminará de este dispositivo. Asegúrate de tener otra forma de acceder a %2$#@accounts@. + other + Tus contraseñas se eliminarán de este dispositivo. Asegúrate de tener otra forma de acceder a %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + cuenta + other + cuentas + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tus contraseñas se eliminarán de todos los dispositivos sincronizados. Asegúrate de tener otra forma de acceder a %2$#@accounts@. + other + Tus contraseñas se eliminarán de todos los dispositivos sincronizados. Asegúrate de tener otra forma de acceder a%2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + cuenta + other + cuentas + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d contraseña borrada + other + %1$d contraseñas borradas + + diff --git a/DuckDuckGo/et.lproj/Localizable.strings b/DuckDuckGo/et.lproj/Localizable.strings index d0c8c88798..59cb7f715a 100644 --- a/DuckDuckGo/et.lproj/Localizable.strings +++ b/DuckDuckGo/et.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL on kopeeritud"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Kustuta"; /* Disable protection action */ "action.title.disable.protection" = "Keela privaatsuse kaitse"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kui DuckDuckGo tuvastab külastatavatel veebisaitidel küpsiste nõusoleku hüpikaknad, saame proovida automaatselt seadistada sinu küpsise-eelistused selliselt, et minimeerida küpsised ja maksimeerida privaatsust, ning seejärel hüpikaknad sulgeda. Mõned saidid ei paku võimalust küpsise-eelistuste haldamiseks, seega saame sellised hüpikaknad ainult peita."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Luba Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Kasutajanimi on kopeeritud"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentige nüüd"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentige kõigi paroolide kustutamiseks"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autendi end, et kinnitada kõigi paroolide kustutamine"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Veebisaidi URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Kustuta parool"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Kas oled kindel, et soovid selle parooli kustutada?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Parooli muutmine"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Sulge"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Kustuta kõik"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Paroolide salvestamine ja automaatne sisestamine"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Tulemusi pole"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Paroolid salvestatakse turvaliselt sinu seadmesse."; diff --git a/DuckDuckGo/et.lproj/Localizable.stringsdict b/DuckDuckGo/et.lproj/Localizable.stringsdict index 16dd9cc09c..5e456c49c0 100644 --- a/DuckDuckGo/et.lproj/Localizable.stringsdict +++ b/DuckDuckGo/et.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Blokeerisin nad! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parool + other + %1$d parooli + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Kas soovid kindlasti %1$d parooli kustutada? + other + Kas soovid kindlasti %1$d parooli kustutada? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Teie parool kustutatakse sellest seadmest. Veenduge, et teil on endiselt võimalus oma %2$#@accounts@ juurde pääseda. + other + Teie paroolid kustutatakse sellest seadmest. Veenduge, et teil on endiselt võimalus pääseda ligi oma %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontod + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Teie parool kustutatakse kõikidest sünkroonitud seadmetest. Veenduge, et teil on endiselt võimalus oma %2$#@accounts@ juurde pääseda. + other + Teie paroolid kustutatakse kõikidest sünkroonitud seadmetest. Veenduge, et teil on endiselt võimalus oma %2$#@accounts@ juurde pääseda. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontod + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parool kustutatud + other + %1$d parooli kustutatud + + diff --git a/DuckDuckGo/fi.lproj/Localizable.strings b/DuckDuckGo/fi.lproj/Localizable.strings index d6410ae06a..ae08f1f9b8 100644 --- a/DuckDuckGo/fi.lproj/Localizable.strings +++ b/DuckDuckGo/fi.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL kopioitu"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Poista"; /* Disable protection action */ "action.title.disable.protection" = "Poista yksityisyyden suoja käytöstä"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kun DuckDuckGo havaitsee evästeiden hallinnan ponnahdusikkunoita vierailemillasi sivustoilla, voimme yrittää määrittää evästeasetuksesi automaattisesti evästeiden minimoimiseksi ja yksityisyyden maksimoimiseksi ja sulkea sitten ponnahdusikkunat. Joillakin sivustoilla evästeiden hallinta ei ole mahdollista, joten voimme vain piilottaa tällaiset ponnahdusikkunat."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Ota Email Protection käyttöön"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Käyttäjätunnus kopioitu"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Todenna nyt"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Todenna poistaaksesi kaikki salasanat"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Vahvista, että haluat poistaa kaikki salasanat."; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Sivuston URL-osoite"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Poista salasana"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Haluatko varmasti poistaa tämän salasanan?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Muokkaa salasanaa"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Sulje"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Poista kaikki"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Tallenna ja täytä salasanat automaattisesti"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ei tuloksia"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Salasanat tallennetaan laitteellesi turvallisesti."; diff --git a/DuckDuckGo/fi.lproj/Localizable.stringsdict b/DuckDuckGo/fi.lproj/Localizable.stringsdict index ff02b2e383..44958a4bcf 100644 --- a/DuckDuckGo/fi.lproj/Localizable.stringsdict +++ b/DuckDuckGo/fi.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d salasana + other + %1$d salasanaa + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Haluatko varmasti poistaa %1$d salasanan? + other + Haluatko varmasti poistaa %1$d salasanaa? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Salasanasi poistetaan tästä laitteesta. Varmista, että pääset yhä käyttämään %2$#@accounts@. + other + Salasanasi poistetaan tästä laitteesta. Varmista, että pääset yhä käyttämään %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + tiliäsi + other + tilejäsi + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Salasanasi poistetaan kaikista synkronoiduista laitteista. Varmista, että pääset yhä käyttämään %2$#@accounts@. + other + Salasanasi poistetaan kaikista synkronoiduista laitteista. Varmista, että pääset yhä käyttämään %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + tiliäsi + other + tilejäsi + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d salasana poistettu + other + %1$d salasanaa poistettu + + diff --git a/DuckDuckGo/fr.lproj/Localizable.strings b/DuckDuckGo/fr.lproj/Localizable.strings index 75303ca4ee..8a65d815c2 100644 --- a/DuckDuckGo/fr.lproj/Localizable.strings +++ b/DuckDuckGo/fr.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiée"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Supprimer"; /* Disable protection action */ "action.title.disable.protection" = "Désactiver la protection de la confidentialité"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Lorsque DuckDuckGo détecte les fenêtres contextuelles de consentement aux cookies sur les sites que vous visitez, vos préférences peuvent, dans la mesure du possible, être configurées automatiquement de façon à ce que les cookies soient réduits au minimum et la confidentialité maximisée, puis les fenêtres contextuelles sont fermées. Certains sites ne proposent pas d'option pour gérer les préférences en matière de cookies. Nous ne pouvons donc masquer que des fenêtres contextuelles comme celles-ci."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Activez Email Protection"; @@ -458,7 +470,7 @@ "autofill.keep-enabled.alert.disable" = "Désactiver"; /* Confirm action for alert when asking the user if they want to keep using autofill */ -"autofill.keep-enabled.alert.keep-using" = "Encore des économies"; +"autofill.keep-enabled.alert.keep-using" = "Continuer à les sauvegarder"; /* Message for alert when asking the user if they want to keep using autofill */ "autofill.keep-enabled.alert.message" = "Cette fonction peut être désactivée à tout moment dans Paramètres."; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nom d'utilisateur copié"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Authentifiez-vous maintenant"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Authentifiez-vous pour supprimer tous les mots de passe"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Authentifiez-vous pour confirmer que vous souhaitez supprimer tous les mots de passe"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL du site Web"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Supprimer le mot de passe"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Voulez-vous vraiment supprimer ce mot de passe ?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Modifier le mot de passe"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Fermer"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Tout supprimer"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Enregistrer et saisir automatiquement les mots de passe"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Aucun résultat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Les mots de passe sont stockés en toute sécurité sur votre appareil."; diff --git a/DuckDuckGo/fr.lproj/Localizable.stringsdict b/DuckDuckGo/fr.lproj/Localizable.stringsdict index 07e2c102c8..bc855b5152 100644 --- a/DuckDuckGo/fr.lproj/Localizable.stringsdict +++ b/DuckDuckGo/fr.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Je les ai bloqués ! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d mot de passe + other + %1$d mots de passe + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Voulez-vous vraiment supprimer %1$d mot de passe ? + other + Voulez-vous vraiment supprimer %1$d mots de passe ? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Votre mot de passe sera supprimé de cet appareil. Assurez-vous d'avoir toujours un moyen d'accéder à votre %2$#@accounts@. + other + Vos mots de passe seront supprimés de cet appareil. Assurez-vous d'avoir toujours un moyen d'accéder à vos %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + compte + other + comptes + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Votre mot de passe sera supprimé de tous les appareils synchronisés. Assurez-vous d'avoir toujours un moyen d'accéder à votre %2$#@accounts@. + other + Vos mots de passe seront supprimés de tous les appareils synchronisés. Assurez-vous d'avoir toujours un moyen d'accéder à vos %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + compte + other + comptes + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d mot de passe supprimé + other + %1$d mots de passe supprimés + + diff --git a/DuckDuckGo/hr.lproj/Localizable.strings b/DuckDuckGo/hr.lproj/Localizable.strings index c85e257952..e60babbc56 100644 --- a/DuckDuckGo/hr.lproj/Localizable.strings +++ b/DuckDuckGo/hr.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL je kopiran"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Izbriši"; /* Disable protection action */ "action.title.disable.protection" = "Onemogući zaštitu privatnosti"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kada DuckDuckGo otkrije skočne prozore pristanka na kolačiće na web lokacijama koje posjećuješ, mi možemo pokušati automatski odrediti tvoje postavke kolačića kako bismo minimizirali kolačiće i maksimizirali privatnost, a zatim zatvorili skočne prozore. Neke web lokacije ne nude opciju upravljanja postavkama kolačića, tako da možemo sakriti samo ovakve skočne prozore."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Omogući Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Korisničko ime je kopirano"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Potvrdi autentičnost"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Potvrdi autentičnost za brisanje svih lozinki"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Izvrši autentifikaciju i time potvrdi da želiš izbrisati sve lozinke"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL web lokacije"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Izbriši lozinku"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Sigurno želiš izbrisati ovu lozinku?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Uredi lozinku"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zatvori"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Izbriši sve"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Spremi i automatski popuni lozinke"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nema rezultata"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Lozinke su sigurno pohranjene na tvom uređaju."; diff --git a/DuckDuckGo/hr.lproj/Localizable.stringsdict b/DuckDuckGo/hr.lproj/Localizable.stringsdict index 88cd477d50..2d3c124795 100644 --- a/DuckDuckGo/hr.lproj/Localizable.stringsdict +++ b/DuckDuckGo/hr.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Ja sam ih blokirao! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d lozinka + few + %1$d lozinke + many + %1$d lozinki + other + %1$d lozinki + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Sigurno želiš izbrisati %1$d lozinku? + few + Sigurno želiš izbrisati %1$d lozinke? + many + Sigurno želiš izbrisati %1$d lozinki? + other + Sigurno želiš izbrisati %1$d lozinki? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoja lozinka bit će izbrisana s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + few + Tvoje lozinke bit će izbrisane s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + many + Tvoje lozinke bit će izbrisane s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + other + Tvoje lozinke bit će izbrisane s ovog uređaja. Provjeri imaš li još uvijek način pristupa svom %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + račun + few + računa + many + računa + other + računa + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tvoja lozinka bit će izbrisana sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa %2$#@accounts@ + few + Tvoje lozinke bit će izbrisane sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa svojim %2$#@accounts@ + many + Tvoje lozinke bit će izbrisane sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa svojim %2$#@accounts@ + other + Tvoje lozinke bit će izbrisane sa svih sinkroniziranih uređaja. Uvjeri se da i dalje imaš način pristupa svojim %2$#@accounts@ + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + računu + few + računima + many + računima + other + računima + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Izbrisana je %1$d lozinka + few + Izbrisane su %1$d lozinke + many + Izbrisano je %1$d lozinki + other + Izbrisano je %1$d lozinki + + diff --git a/DuckDuckGo/hu.lproj/Localizable.strings b/DuckDuckGo/hu.lproj/Localizable.strings index 45a918ddae..e766dd8db6 100644 --- a/DuckDuckGo/hu.lproj/Localizable.strings +++ b/DuckDuckGo/hu.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL lemásolva"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Törlés"; /* Disable protection action */ "action.title.disable.protection" = "Adatvédelem letiltása"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Amikor a DuckDuckGo a meglátogatott webhelyeken sütik elfogadására szolgáló felugró ablakokat észlel, a sütik minimalizálása és az adatvédelem maximalizálása érdekében automatikusan megpróbálhatjuk megadni a sütikre vonatkozó beállításaidat, és bezárhatjuk a felugró ablakokat. Egyes webhelyek nem adnak lehetőséget a sütikre vonatkozó beállítások kezelésére, így csak az ilyen felugró ablakokat tudjuk elrejteni."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "E-mail-védelem engedélyezése"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Felhasználónév másolva"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Hitelesítés"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Hitelesítés minden jelszó törléséhez"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Hitelesítéssel erősítsd meg, hogy minden jelszót törölni szeretnél"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Webhely URL-címe"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Jelszó törlése"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Biztosan törlöd ezt a jelszót?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Jelszó szerkesztése"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Bezárás"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Összes törlése"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Jelszavak mentése és automatikus kitöltése"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nincs találat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "A jelszavakat az eszközöd biztonságosan tárolja."; diff --git a/DuckDuckGo/hu.lproj/Localizable.stringsdict b/DuckDuckGo/hu.lproj/Localizable.stringsdict index 06695acfb1..0c212bd7ea 100644 --- a/DuckDuckGo/hu.lproj/Localizable.stringsdict +++ b/DuckDuckGo/hu.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Blokkoltam őket! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d jelszó + other + %1$d jelszó + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Biztosan törölni szeretnél %1$d jelszót? + other + Biztosan törölni szeretnél %1$d jelszót? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A jelszó törölve lesz erről az eszközről. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + other + A jelszavak törölve lesznek erről az eszközről. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + fiókodhoz + other + fiókjaidhoz + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A jelszó minden szinkronizált eszközről törölve lesz. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + other + A jelszavak minden szinkronizált eszközről törölve lesznek. Győződj meg róla, hogy továbbra is hozzáférsz a %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + fiókodhoz + other + fiókjaidhoz + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d jelszó törölve + other + %1$d jelszó törölve + + diff --git a/DuckDuckGo/it.lproj/Localizable.strings b/DuckDuckGo/it.lproj/Localizable.strings index bb2ac6be4c..9299050613 100644 --- a/DuckDuckGo/it.lproj/Localizable.strings +++ b/DuckDuckGo/it.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiato"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Cancella"; /* Disable protection action */ "action.title.disable.protection" = "Disattiva la tutela della privacy"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Quando DuckDuckGo rileva la presenza di popup per il consenso ai cookie sui siti che visiti, può tentare di impostare automaticamente le tue preferenze in modo da ridurre al minimo i cookie e aumentare la privacy, chiudendo poi i popup. Alcuni siti non forniscono un'opzione di gestione delle preferenze sui cookie, quindi possiamo nascondere solo popup come questi."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Abilita Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nome utente copiato"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autenticati adesso"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Effettua l'autenticazione per eliminare tutte le password"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Esegui l'autenticazione per confermare l'eliminazione di tutte le password"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL del sito Web"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Elimina password"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Eliminare questa password?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Modifica password"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Chiudi"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Elimina tutto"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Salva e compila automaticamente le password"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nessun risultato"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Le password sono archiviate in modo sicuro sul tuo dispositivo."; diff --git a/DuckDuckGo/it.lproj/Localizable.stringsdict b/DuckDuckGo/it.lproj/Localizable.stringsdict index 5b242a0c34..34ecb1249d 100644 --- a/DuckDuckGo/it.lproj/Localizable.stringsdict +++ b/DuckDuckGo/it.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Li ho bloccati! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d password + other + %1$d password + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Eliminare %1$d password? + other + Eliminare %1$d password? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Le password verranno eliminate da questo dispositivo. Assicurati di avere ancora la possibilità di accedere al tuo %2$#@accounts@. + other + Le password verranno eliminate da questo dispositivo. Assicurati di avere ancora la possibilità di accedere ai tuoi %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + account + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Le password verranno eliminate da tutti i dispositivi sincronizzati. Assicurati di avere ancora la possibilità di accedere al tuo %2$#@accounts@. + other + Le password verranno eliminate da tutti i dispositivi sincronizzati. Assicurati di avere ancora la possibilità di accedere ai tuoi %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + account + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d password eliminata + other + %1$d password eliminate + + diff --git a/DuckDuckGo/lt.lproj/Localizable.strings b/DuckDuckGo/lt.lproj/Localizable.strings index 0b243fbd45..16acb6bb3d 100644 --- a/DuckDuckGo/lt.lproj/Localizable.strings +++ b/DuckDuckGo/lt.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL nukopijuotas"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Trinti"; /* Disable protection action */ "action.title.disable.protection" = "Išjungti privatumo apsaugą"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kai „DuckDuckGo“ jūsų lankomose svetainėse aptinka sutikimo su slapukais iškylančiuosius langus, galime automatiškai nustatyti jūsų slapukų nuostatas, kad sumažintume slapukų skaičių, sustiprintume privatumą ir uždarytume iškylančiuosius langus. Kai kuriose svetainėse nėra galimybės tvarkyti slapukų nuostatas, todėl galime tik paslėpti tokius iškylančiuosius langus."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Įjungti „Email Protection“"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Naudotojo vardas nukopijuotas"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Patvirtinkite autentiškumą dabar"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Patvirtinkite autentiškumą, kad ištrintumėte visus slaptažodžius"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Patvirtinkite tapatybę, kad patvirtintumėte, jog norite ištrinti visus slaptažodžius"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Svetainės URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Ištrinti slaptažodį"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Ar tikrai norite ištrinti šį slaptažodį?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Redaguoti slaptažodį"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Uždaryti"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Ištrinti viską"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Išsaugokite ir automatiškai užpildykite slaptažodžius"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Rezultatų nerasta"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Slaptažodžiai saugiai saugomi jūsų įrenginyje."; diff --git a/DuckDuckGo/lt.lproj/Localizable.stringsdict b/DuckDuckGo/lt.lproj/Localizable.stringsdict index b0614a1ab2..feddc77cf3 100644 --- a/DuckDuckGo/lt.lproj/Localizable.stringsdict +++ b/DuckDuckGo/lt.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Užblokavau juos! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d slaptažodžis + few + %1$d slaptažodžiai + many + %1$d slaptažodžio + other + %1$d slaptažodžių + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ar tikrai norite ištrinti %1$d slaptažodį? + few + Ar tikrai norite ištrinti %1$d slaptažodžius? + many + Ar tikrai norite ištrinti %1$d slaptažodžio? + other + Ar tikrai norite ištrinti %1$d slaptažodžių? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + few + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + many + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + other + Jūsų slaptažodžiai bus ištrinti iš šio įrenginio. Įsitikinkite, kad vis dar turite galimybę pasiekti savo %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + sąskaita + few + sąskaitos + many + sąskaitos + other + sąskaitų + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyrą. + few + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyras. + many + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyros. + other + Jūsų slaptažodžiai bus ištrinti iš visų sinchronizuojamų įrenginių. Įsitikinkite, kad vis dar turite būdą, kaip pasiekti %2$#@accounts@ paskyrų. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + sąskaita + few + sąskaitos + many + sąskaitos + other + sąskaitų + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d slaptažodis ištrintas + few + %1$d slaptažodžiai ištrinti + many + %1$d slaptažodžio ištrinta + other + %1$d slaptažodžių ištrinti + + diff --git a/DuckDuckGo/lv.lproj/Localizable.strings b/DuckDuckGo/lv.lproj/Localizable.strings index c163f07853..211afd152a 100644 --- a/DuckDuckGo/lv.lproj/Localizable.strings +++ b/DuckDuckGo/lv.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL ir nokopēts"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Dzēst"; /* Disable protection action */ "action.title.disable.protection" = "Atspējot privātuma aizsardzību"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Ja DuckDuckGo tevis apmeklētajās vietnēs atklāj sīkfailu piekrišanas uznirstošos logus, mēs varam automātiski iestatīt tavas sīkfailu preferences, lai samazinātu sīkfailus un stiprinātu privātumu, un pēc tam aizvērt šos uznirstošos logus. Dažās vietnēs nav iespējas pārvaldīt sīkfailu preferences, tāpēc mēs varam tikai paslēpt šādus uznirstošos logus."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Iespējo e-pasta aizsardzību"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Lietotājvārds nokopēts"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentificēt tagad"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentificē, lai dzēstu visas paroles"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentificējies, lai apstiprinātu, ka vēlies dzēst visas paroles"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Tīmekļa vietnes URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Dzēst paroli"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Vai tiešām vēlies dzēst šo paroli?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Paroles rediģēšana"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Aizvērt"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Dzēst visus"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Saglabāt un automātiski aizpildīt paroles"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Nav rezultātu"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Paroles tiek droši glabātas tavā ierīcē."; diff --git a/DuckDuckGo/lv.lproj/Localizable.stringsdict b/DuckDuckGo/lv.lproj/Localizable.stringsdict index a4d519bc52..9e4a7ba870 100644 --- a/DuckDuckGo/lv.lproj/Localizable.stringsdict +++ b/DuckDuckGo/lv.lproj/Localizable.stringsdict @@ -172,5 +172,121 @@ Es tos nobloķēju! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + %1$d paroles + one + %1$d parole + other + %1$d paroles + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Vai tiešām vēlies dzēst %1$d paroles? + one + Vai tiešām vēlies dzēst %1$d paroli? + other + Vai tiešām vēlies dzēst %1$d paroles? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Tavas paroles tiks dzēstas no šīs ierīces. Pārliecinies, ka joprojām vari piekļūt saviem %2$#@accounts@ kontiem. + one + Tavas paroles tiks dzēstas no šīs ierīces. Pārliecinies, ka joprojām vari piekļūt savam %2$#@accounts@ kontam. + other + Tavas paroles tiks dzēstas no šīs ierīces. Pārliecinies, ka joprojām vari piekļūt saviem %2$#@accounts@ kontiem. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + konti + one + konts + other + konti + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Tavas paroles tiks dzēstas no visām sinhronizētajām ierīcēm. Pārliecinies, vai joprojām varēsi piekļūt saviem %2$#@accounts@ kontiem. + one + Tavas paroles tiks dzēstas no visām sinhronizētajām ierīcēm. Pārliecinies, vai joprojām varēsi piekļūt savam %2$#@accounts@ kontam. + other + Tavas paroles tiks dzēstas no visām sinhronizētajām ierīcēm. Pārliecinies, vai joprojām varēsi piekļūt saviem %2$#@accounts@ kontiem. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + konti + one + konts + other + konti + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + %1$d paroles izdzēstas + one + %1$d parole izdzēsta + other + %1$d paroles izdzēstas + + diff --git a/DuckDuckGo/nb.lproj/Localizable.strings b/DuckDuckGo/nb.lproj/Localizable.strings index f0880a9b16..d7ab1b2eed 100644 --- a/DuckDuckGo/nb.lproj/Localizable.strings +++ b/DuckDuckGo/nb.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Nettadresse kopiert"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Slett"; /* Disable protection action */ "action.title.disable.protection" = "Deaktiver personvernbeskyttelse"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Når DuckDuckGo oppdager popup-vinduer om samtykke til informasjonskapsler på nettsteder du besøker, kan vi automatisk angi innstillingene for å minimere informasjonskapslene og maksimere personvernet, og deretter lukke popup-vinduene. Noen nettsteder tilbyr ikke noe alternativ for å administrere preferanser for informasjonskapsler, så slike popup-vinduer kan vi bare skjule."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Aktiver e-postbeskyttelse"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Brukernavnet er kopiert"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Godkjenn nå"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Godkjenn for å slette alle passord"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Godkjenn for å bekrefte at du vil slette alle passord"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL-adresse til nettsted"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Slett passord"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Er du sikker på at du vil slette dette passordet?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Rediger passordet"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Lukk"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Slett alt"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Lagre og fyll ut passord automatisk"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ingen resultater"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Passord lagres på enheten din på en sikker måte."; diff --git a/DuckDuckGo/nb.lproj/Localizable.stringsdict b/DuckDuckGo/nb.lproj/Localizable.stringsdict index 6a12a317f3..63a57f94f9 100644 --- a/DuckDuckGo/nb.lproj/Localizable.stringsdict +++ b/DuckDuckGo/nb.lproj/Localizable.stringsdict @@ -192,6 +192,109 @@ Jeg har blokkert dem! %d sporer funnet other %d sporere funnet + + + + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d passord + other + %1$d passord + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Er du sikker på at du vil slette %1$d passord? + other + Er du sikker på at du vil slette %1$d passord? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Passordet ditt blir slettet fra denne enheten. Sørg for at du fortsatt har tilgang til din %2$#@accounts@. + other + Passordene dine blir slettet fra denne enheten. Sørg for at du fortsatt har tilgang til dine %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontoer + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Passordet ditt blir slettet fra alle synkroniserte enheter. Sørg for at du fortsatt har tilgang til din %2$#@accounts@. + other + Passordene dine blir slettet fra alle synkroniserte enheter. Sørg for at du fortsatt har tilgang til dine %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + kontoer + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d passord er slettet + other + %1$d passord er slettet diff --git a/DuckDuckGo/nl.lproj/Localizable.strings b/DuckDuckGo/nl.lproj/Localizable.strings index 11d462af04..a618d838f7 100644 --- a/DuckDuckGo/nl.lproj/Localizable.strings +++ b/DuckDuckGo/nl.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL gekopieerd"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Verwijderen"; /* Disable protection action */ "action.title.disable.protection" = "Privacybescherming uitschakelen"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Wanneer DuckDuckGo pop-ups voor cookietoestemming op bezochte websites detecteert, kunnen we proberen automatisch je cookievoorkeuren in te stellen om het aantal cookies tot een minimum te beperken en je privacy te maximaliseren. De pop-ups worden vervolgens gesloten. Sommige sites bieden geen optie om cookievoorkeuren te beheren, zodat we pop-ups zoals deze alleen kunnen verbergen."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "E-mailbeveiliging inschakelen"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Gebruikersnaam gekopieerd"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Nu verifiëren"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Verifiëren om alle wachtwoorden te verwijderen"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Verifieer om te bevestigen dat je alle wachtwoorden wilt verwijderen"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL van de website"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Wachtwoord verwijderen"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Weet je zeker dat je dit wachtwoord wilt verwijderen?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Wachtwoord bewerken"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Sluiten"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Alles verwijderen"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Wachtwoorden opslaan en automatisch invullen"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Geen resultaten"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Wachtwoorden worden veilig opgeslagen op je apparaat."; diff --git a/DuckDuckGo/nl.lproj/Localizable.stringsdict b/DuckDuckGo/nl.lproj/Localizable.stringsdict index 0c1b4fc3a1..a8c322780f 100644 --- a/DuckDuckGo/nl.lproj/Localizable.stringsdict +++ b/DuckDuckGo/nl.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Ik heb ze geblokkeerd! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d wachtwoord + other + %1$d wachtwoorden + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + one + Weet je zeker dat je %1$d wachtwoord wilt verwijderen? + other + Weet je zeker dat je %1$d wachtwoorden wilt verwijderen? + NSStringFormatValueTypeKey + d + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Je wachtwoord wordt van dit apparaat verwijderd. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + other + Je wachtwoorden worden van dit apparaat verwijderd. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + accounts + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Je wachtwoord wordt verwijderd van alle gesynchroniseerde apparaten. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + other + Je wachtwoorden worden verwijderd van alle gesynchroniseerde apparaten. Zorg ervoor dat je nog steeds toegang hebt tot je %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + accounts + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d wachtwoord verwijderd + other + %1$d wachtwoorden verwijderd + + diff --git a/DuckDuckGo/pl.lproj/Localizable.strings b/DuckDuckGo/pl.lproj/Localizable.strings index 3667ccf72c..f314c24c9a 100644 --- a/DuckDuckGo/pl.lproj/Localizable.strings +++ b/DuckDuckGo/pl.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Skopiowano adres URL"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Usuń"; /* Disable protection action */ "action.title.disable.protection" = "Wyłącz ochronę prywatności"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Kiedy DuckDuckGo wykrywa wyskakujące okienka z prośbą o zgodę na używanie plików cookie na odwiedzanych przez Ciebie stronach, może spróbować automatycznie ustawić Twoje preferencje dotyczące plików cookie w taki sposób, aby zminimalizować liczbę plików cookie i zmaksymalizować prywatność, a następnie zamknąć wyskakujące okienka. Niektóre witryny nie umożliwiają zarządzania preferencjami dotyczącymi plików cookie, dlatego możemy jedynie ukryć takie wyskakujące okienka."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Włącz ochronę poczty Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Skopiowano nazwę użytkownika"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Uwierzytelnij teraz"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Uwierzytelnij, aby usunąć wszystkie hasła"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Uwierzytelnij, aby potwierdzić, że chcesz usunąć wszystkie hasła"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Adres URL witryny"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Usuń hasło"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Czy na pewno chcesz usunąć to hasło?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Edytuj hasło"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zamknij"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Usuń wszystko"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Zapisuj i automatycznie uzupełniaj hasła"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Brak wyników"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Hasła są bezpiecznie przechowywane na Twoim urządzeniu."; diff --git a/DuckDuckGo/pl.lproj/Localizable.stringsdict b/DuckDuckGo/pl.lproj/Localizable.stringsdict index 0f112e9b01..77d5f52835 100644 --- a/DuckDuckGo/pl.lproj/Localizable.stringsdict +++ b/DuckDuckGo/pl.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Zablokowałem ich! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d hasło + few + %1$d hasła + many + %1$d haseł + other + %1$d haseł + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Czy na pewno chcesz usunąć %1$d hasło? + few + Czy na pewno chcesz usunąć %1$d hasła? + many + Czy na pewno chcesz usunąć %1$d haseł? + other + Czy na pewno chcesz usunąć %1$d hasła? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swojego %2$#@accounts@. + few + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swoich %2$#@accounts@. + many + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swoich %2$#@accounts@. + other + Twoje hasła zostaną usunięte z tego urządzenia. Upewnij się, że nadal masz możliwość dostępu do swoich %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + few + konta + many + kont + other + kont + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swojego %2$#@accounts@. + few + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swoich %2$#@accounts@. + many + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swoich %2$#@accounts@. + other + Twoje hasła zostaną usunięte ze wszystkich zsynchronizowanych urządzeń. Upewnij się, że nadal masz dostęp do swoich %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + few + konta + many + kont + other + kont + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Usunięto %1$d hasło + few + Usunięto %1$d hasła + many + Usunięto %1$d haseł + other + Usunięto %1$d hasła + + diff --git a/DuckDuckGo/pt.lproj/Localizable.strings b/DuckDuckGo/pt.lproj/Localizable.strings index 4324f1a86d..19ebbbb4d9 100644 --- a/DuckDuckGo/pt.lproj/Localizable.strings +++ b/DuckDuckGo/pt.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiada"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Eliminar"; /* Disable protection action */ "action.title.disable.protection" = "Desativar Proteção de Privacidade"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Quando o DuckDuckGo deteta pop-ups de consentimento de cookies nos sites que visitas, podemos tentar definir automaticamente as tuas preferências de cookies para minimizar os cookies e maximizar a privacidade e, em seguida, fechar os pop-ups. Alguns sites não te dão a opção de gerir preferências de cookies, por isso só podemos ocultar pop-ups como estes."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Ativa a Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Nome de utilizador copiado"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autenticar agora"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autenticar para eliminar todas as palavras-passe"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentica para confirmar que pretendes eliminar todas as palavras-passe"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL do site"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Eliminar palavra-passe"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Tens a certeza de que pretendes eliminar esta palavra-passe?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Editar palavra-passe"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Fechar"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Eliminar tudo"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Guardar e preencher palavras-passe automaticamente"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Sem resultados"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "As palavras-passe são armazenadas com segurança no teu dispositivo."; diff --git a/DuckDuckGo/pt.lproj/Localizable.stringsdict b/DuckDuckGo/pt.lproj/Localizable.stringsdict index 7c28acc96b..b59d6898ad 100644 --- a/DuckDuckGo/pt.lproj/Localizable.stringsdict +++ b/DuckDuckGo/pt.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Bloqueei-os! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d palavra-passe + other + %1$d palavras-passe + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Tens a certeza de que pretendes eliminar %1$d palavra-passe? + other + Tens a certeza de que pretendes eliminar %1$d palavras-passe? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A tua palavra-passe será eliminada deste dispositivo. Confirma que ainda tens uma forma de aceder à %2$#@accounts@. + other + As tuas palavras-passe serão eliminadas deste dispositivo. Confirma que ainda tens uma forma de aceder às %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + contas + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + A tua palavra-passe será eliminada de todos os dispositivos sincronizados. Confirma que ainda tens uma forma de aceder à %2$#@accounts@. + other + As tuas palavras-passe serão eliminadas de todos os dispositivos sincronizados. Confirma que ainda tens uma forma de aceder às %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + other + contas + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d palavra-passe eliminada + other + %1$d palavras-passe eliminadas + + diff --git a/DuckDuckGo/ro.lproj/Localizable.strings b/DuckDuckGo/ro.lproj/Localizable.strings index 370c94d16f..6b91fde611 100644 --- a/DuckDuckGo/ro.lproj/Localizable.strings +++ b/DuckDuckGo/ro.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL copiat"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Ștergere"; /* Disable protection action */ "action.title.disable.protection" = "Dezactivează protecția confidențialității"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Când DuckDuckGo detectează ferestre pop-up în care ți se solicită consimțământul cu privire la modulele cookie pe site-urile pe care le vizitezi, putem încerca să îți setăm automat preferințele privind modulele cookie pentru a minimiza modulele cookie și a maximiza confidențialitatea și, apoi, pentru a închide ferestrele pop-up. Unele site-uri nu oferă o opțiune de gestionare a preferințelor privind modulele cookie, prin urmare putem doar să ascundem ferestrele pop-up precum acestea."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Activează Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Numele de utilizator a fost copiat"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentifică-te acum"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentifică-te pentru a șterge toate parolele"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentifică-te pentru a confirma că dorești să ștergi toate parolele"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL-ul site-ului"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Șterge parola"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Sigur dorești să ștergi această parolă?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Editează parola"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Închidere"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Șterge toate"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Salvează și completează automat parolele"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Niciun rezultat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Parolele sunt stocate în siguranță pe dispozitivul tău."; diff --git a/DuckDuckGo/ro.lproj/Localizable.stringsdict b/DuckDuckGo/ro.lproj/Localizable.stringsdict index 15eedec91f..22a82ec393 100644 --- a/DuckDuckGo/ro.lproj/Localizable.stringsdict +++ b/DuckDuckGo/ro.lproj/Localizable.stringsdict @@ -172,5 +172,121 @@ I-am blocat! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parolă + few + %1$d parole + other + %1$d de parole + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Sigur dorești să ștergi %1$d parolă? + few + Sigur dorești să ștergi %1$d parole? + other + Sigur dorești să ștergi această %1$d de parole? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Parola ta va fi ștearsă de pe acest dispozitiv. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + few + Parolele tale vor fi șterse de pe acest dispozitiv. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + other + Parolele tale vor fi șterse de pe acest dispozitiv. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + few + conturi + other + de conturi + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Parola ta va fi ștearsă de pe toate dispozitivele sincronizate. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + few + Parolele tale vor fi șterse de pe toate dispozitivele sincronizate. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + other + Parolele dvs. vor fi șterse de pe toate dispozitivele sincronizate. Asigură-te că ai în continuare o modalitate de a-ți accesa %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + account + few + conturi + other + de conturi + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d parolă ștearsă + few + %1$d parole șterse + other + %1$d de parole șterse + + diff --git a/DuckDuckGo/ru.lproj/Localizable.strings b/DuckDuckGo/ru.lproj/Localizable.strings index d63d876074..abab11583c 100644 --- a/DuckDuckGo/ru.lproj/Localizable.strings +++ b/DuckDuckGo/ru.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Адрес скопирован"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Удалить"; /* Disable protection action */ "action.title.disable.protection" = "Отключить защиту конфиденциальности"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Если DuckDuckGo обнаружит всплывающее окно выбора куки-файлов на каком-либо сайте, мы попытаемся автоматически выбрать минимум куки, максимально защитив ваши данные, а затем закроем окно. Некоторые сайты не позволяют контролировать настройки куки-файлов. В этом случае мы можем их только скрыть."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Включите защиту электронной почты"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Имя пользователя скопировано!"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Авторизоваться"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Авторизация для удаления паролей"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Чтобы подтвердить удаление, выполните авторизацию"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Адрес сайта"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Удалить пароль"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Вы точно хотите удалить этот пароль?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Редактирование пароля"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Закрыть"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Удалить все"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Хранение и автозаполнение паролей"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Нет результатов"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Пароли надежно хранятся на вашем устройстве."; diff --git a/DuckDuckGo/ru.lproj/Localizable.stringsdict b/DuckDuckGo/ru.lproj/Localizable.stringsdict index 33e60942d5..7b4f8bf3ba 100644 --- a/DuckDuckGo/ru.lproj/Localizable.stringsdict +++ b/DuckDuckGo/ru.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d пароль + few + %1$d пароля + many + %1$d паролей + other + %1$d пароля + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Вы точно хотите удалить %1$d пароль? + few + Вы точно хотите удалить %1$d пароля? + many + Вы точно хотите удалить %1$d паролей? + other + Вы точно хотите удалить пароли (%1$d)? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свою %2$#@accounts@. + few + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + many + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + other + Ваши пароли будут удалены с этого устройства. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + учетную запись + few + учетные записи + many + учетные записи + other + учетные записи + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свою %2$#@accounts@. + few + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + many + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + other + Пароли будут удалены со всех синхронизированных устройств. Убедитесь, что вы по-прежнему можете войти в свои %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + учетную запись + few + учетные записи + many + учетные записи + other + учетные записи + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Удален %1$d пароль + few + Удалено %1$d пароля + many + Удалено %1$d паролей + other + Удалено паролей: %1$d + + diff --git a/DuckDuckGo/sk.lproj/Localizable.strings b/DuckDuckGo/sk.lproj/Localizable.strings index 09f96b80ca..6826707e50 100644 --- a/DuckDuckGo/sk.lproj/Localizable.strings +++ b/DuckDuckGo/sk.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Adresa URL bola skopírovaná"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Vymazať"; /* Disable protection action */ "action.title.disable.protection" = "Vypnúť ochranu súkromia"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "Keď DuckDuckGo zistí na vami navštívených lokalitách kontextové okná týkajúce sa súhlasu so súbormi cookie, môžeme sa pokúsiť automaticky nastaviť vaše možnosti súborov cookie tak, aby sa využívanie súborov cookie minimalizovali a aby sa maximalizovala ochrana súkromia, a potom môžeme kontextové automaticky okná zavrieť. Niektoré lokality neponúkajú možnosť spravovať preferencie súborov cookie, takže takéto kontextové okná môžeme iba skryť."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Povoliť ochranu e-mailu"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Meno používateľa bolo skopírované"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Overiť teraz"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Overenie na odstránenie všetkých hesiel."; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Overením potvrďte, že chcete odstrániť všetky heslá."; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Adresa URL webových stránok"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Odstrániť heslo"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Naozaj chcete odstrániť toto heslo?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Upraviť heslo"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zatvoriť"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Odstrániť všetko"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Ukladať a automaticky dopĺňať heslá"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Žiadne výsledky"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Heslá sú bezpečne uložené vo vašom zariadení."; diff --git a/DuckDuckGo/sk.lproj/Localizable.stringsdict b/DuckDuckGo/sk.lproj/Localizable.stringsdict index afe4bb849c..4361597c30 100644 --- a/DuckDuckGo/sk.lproj/Localizable.stringsdict +++ b/DuckDuckGo/sk.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Boli zablokovaní! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d heslo + few + %1$d heslá + many + %1$d hesla + other + %1$d hesiel + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Naozaj chcete odstrániť %1$d heslo? + few + Naozaj chcete odstrániť %1$d heslá? + many + Naozaj chcete odstrániť %1$d hesla? + other + Naozaj chcete odstrániť %1$d hesiel? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Vaše heslo bude odstránené z tohto zariadenia. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + few + Vaše heslá budú odstránené z tohto zariadenia. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + many + Vaše heslá budú odstránené z tohto zariadenia. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + other + Vaše heslá budú z tohto zariadenia vymazané. Uistite sa, že stále máte prístup k svojim %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účet + few + účty + many + účtu + other + účtov + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Vaše heslo bude odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + few + Vaše heslá budú odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + many + Vaše heslá budú odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + other + Vaše heslá budú odstránené zo všetkých synchronizovaných zariadení. Uistite sa, že stále máte prístup k svojim%2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + účet + few + účty + many + účtu + other + účtov + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Bolo odstránené %1$d heslo + few + Boli odstránené %1$d heslá + many + Bolo odstránených %1$d hesiel + other + Bol odstránených %1$d hesiel + + diff --git a/DuckDuckGo/sl.lproj/Localizable.strings b/DuckDuckGo/sl.lproj/Localizable.strings index 8b11995d70..8fb6b6af7f 100644 --- a/DuckDuckGo/sl.lproj/Localizable.strings +++ b/DuckDuckGo/sl.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL naslov kopiran"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Izbriši"; /* Disable protection action */ "action.title.disable.protection" = "Onemogoči zaščito zasebnosti"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "DuckDuckGo na spletnih mestih, ki jih obiščete, zazna pojavna okna s soglasjem za piškotke, zato lahko samodejno nastavimo vaše nastavitve piškotkov, da zmanjšamo uporabo piškotkov in povečamo zasebnost, nato pa pojavna okna zapremo. Nekatera spletna mesta pa ne omogočajo upravljanja nastavitev piškotkov, zato lahko takšna pojavna okna le skrijemo."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Omogočite zaščito e-pošte Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Uporabniško ime je bilo kopirano"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Preveri pristnost zdaj"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Preverite pristnost za brisanje vseh gesel"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Potrdite, da želite izbrisati vsa gesla"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "URL spletnega mesta"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Izbriši geslo"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Ali ste prepričani, da želite izbrisati to geslo?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Uredite geslo"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Zapri"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Izbriši vse"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Shranite in samodejno izpolnite gesla"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Ni rezultatov"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Gesla so varno shranjena v vaši napravi."; diff --git a/DuckDuckGo/sl.lproj/Localizable.stringsdict b/DuckDuckGo/sl.lproj/Localizable.stringsdict index 988805afa3..a42f7c1e50 100644 --- a/DuckDuckGo/sl.lproj/Localizable.stringsdict +++ b/DuckDuckGo/sl.lproj/Localizable.stringsdict @@ -190,5 +190,135 @@ Blokiral sem jih! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d geslo + two + %1$d gesli + few + %1$d gesla + other + %1$d gesel + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ali ste prepričani, da želite izbrisati %1$d geslo? + two + Ali ste prepričani, da želite izbrisati %1$d gesli? + few + Ali ste prepričani, da želite izbrisati %1$d gesla? + other + Ali ste prepričani, da želite izbrisati %1$d gesel? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojega %2$#@accounts@. + two + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojih %2$#@accounts@. + few + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojih %2$#@accounts@. + other + Vaša gesla bodo izbrisana iz te naprave. Prepričajte se, da še vedno lahko dostopate do svojih %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + račun + two + računa + few + računi + other + računov + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojega %2$#@accounts@. + two + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojih %2$#@accounts@. + few + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojih %2$#@accounts@. + other + Gesla bodo izbrisana iz vseh sinhroniziranih naprav. Prepričajte se, da imate še vedno način za dostop do svojih %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + račun + two + računa + few + računi + other + računov + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d geslo je bilo izbrisano + two + %1$d gesli sta bili izbrisani + few + %1$d gesla so bila izbrisana + other + %1$d gesel je bilo izbrisanih + + diff --git a/DuckDuckGo/sv.lproj/Localizable.strings b/DuckDuckGo/sv.lproj/Localizable.strings index 626ec73894..b03ac806df 100644 --- a/DuckDuckGo/sv.lproj/Localizable.strings +++ b/DuckDuckGo/sv.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "Webbadress kopierad"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Ta bort"; /* Disable protection action */ "action.title.disable.protection" = "Inaktivera integritetsskydd"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "När DuckDuckGo upptäcker popup-fönster för godkännande av cookies på webbplatser som du besöker kan vi försöka att automatiskt ställa in dina cookieinställningar på att minimera cookies och maximera din integritet, och därefter stänga popup-fönstren. På vissa webbplatser finns ingen möjlighet att justera cookieinställningarna, så vi kan enbart dölja popup-fönster som dessa."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Aktivera Email Protection"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Användarnamn kopierat"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Autentisera nu"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Autentisera för att ta bort alla lösenord"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Autentisera för att bekräfta att du vill radera alla lösenord"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Webbplats-URL"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Radera lösenord"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Är du säker på att du vill radera detta lösenord?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Redigera lösenord"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Stäng"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Radera allt"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Spara och fyll i lösenord automatiskt"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Inga resultat"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Lösenord lagras säkert på din enhet."; diff --git a/DuckDuckGo/sv.lproj/Localizable.stringsdict b/DuckDuckGo/sv.lproj/Localizable.stringsdict index 87d22e38fc..2b260781eb 100644 --- a/DuckDuckGo/sv.lproj/Localizable.stringsdict +++ b/DuckDuckGo/sv.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Jag blockerade dem! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d lösenord + other + %1$d lösenord + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Är du säker på att du vill radera %1$d lösenord? + other + Är du säker på att du vill radera %1$d lösenord? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ditt lösenord kommer att raderas från denna enhet. Se till att du har kvar ett sätt att komma åt ditt %2$#@accounts@. + other + Dina lösenord kommer att raderas från denna enhet. Se till att du har kvar ett sätt att komma åt dina %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konton + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Ditt lösenord kommer att raderas från alla synkroniserade enheter. Se till att du har kvar ett sätt att komma åt ditt %2$#@accounts@. + other + Dina lösenord kommer att raderas från alla synkroniserade enheter. Se till att du har kvar ett sätt att komma åt dina %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + konto + other + konton + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d lösenord raderat + other + %1$d lösenord raderade + + diff --git a/DuckDuckGo/tr.lproj/Localizable.strings b/DuckDuckGo/tr.lproj/Localizable.strings index 2e53912f04..6d58c61947 100644 --- a/DuckDuckGo/tr.lproj/Localizable.strings +++ b/DuckDuckGo/tr.lproj/Localizable.strings @@ -38,7 +38,7 @@ "action.title.copy.message" = "URL kopyalandı"; /* Delete action - button shown in alert */ -"action.title.delete" = "Delete"; +"action.title.delete" = "Sil"; /* Disable protection action */ "action.title.disable.protection" = "Gizlilik Korumasını Devre Dışı Bırak"; @@ -448,6 +448,18 @@ /* No comment provided by engineer. */ "autoconsent.info.header" = "DuckDuckGo ziyaret ettiğiniz sitelerde çerez onayı açılır pencereleri tespit ederse çerez tercihlerinizi çerezleri en aza indirecek ve gizliliği en üst düzeye çıkaracak şekilde otomatik olarak ayarlayabilir ve ardından açılır pencereleri kapatabiliriz. Bazı siteler çerez tercihlerini yönetmek için bir seçenek sunmaz. Bu nedenle yalnızca bu gibi açılır pencereleri gizleyebiliriz."; +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.completion" = "autofill.delete.all.passwords.completion"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.body" = "autofill.delete.all.passwords.confirmation.body"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.confirmation.title" = "autofill.delete.all.passwords.confirmation.title"; + +/* Do not translate - stringsdict entry */ +"autofill.delete.all.passwords.sync.confirmation.body" = "autofill.delete.all.passwords.sync.confirmation.body"; + /* Text link to email protection website */ "autofill.enable.email.protection" = "Email Protection'ı Etkinleştir"; @@ -490,6 +502,15 @@ /* Title for toast when copying username */ "autofill.logins.copy-toast.username-copied" = "Kullanıcı adı kopyalandı"; +/* Title of button in prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.button" = "Kimlik Doğrulaması Yapın"; + +/* Title of prompt requiring authentication before all passwords are deleted */ +"autofill.logins.delete.all.authentication.prompt.title" = "Tüm Şifreleri Silmek İçin Kimliğinizi Doğrulayın"; + +/* Reason for authentication when deleting all logins */ +"autofill.logins.delete.all.authentication.reason" = "Tüm şifreleri silmek istediğinizi onaylamak için kimlik doğrulaması yapın"; + /* Address label for login details on autofill */ "autofill.logins.details.address" = "Web sitesi URL'si"; @@ -502,9 +523,6 @@ /* Autofill alert button confirming delete autofill login */ "autofill.logins.details.delete-confirmation.button" = "Şifreyi Sil"; -/* Title of confirmation alert when deleting an autofill login */ -"autofill.logins.details.delete-confirmation.title" = "Bu şifreyi silmek istediğinizden emin misiniz?"; - /* Title when editing autofill login details */ "autofill.logins.details.edit-title" = "Şifreyi Düzenle"; @@ -565,6 +583,9 @@ /* Title for close navigation button */ "autofill.logins.list.close-title" = "Kapat"; +/* Title for button to delete all saved autofill passwords */ +"autofill.logins.list.delete.all" = "Tümünü Sil"; + /* Title for a toggle that enables autofill */ "autofill.logins.list.enable" = "Şifreleri kaydedin ve otomatik doldurun"; @@ -628,6 +649,9 @@ /* Title displayed when there are no results on Autofill search */ "autofill.logins.search.no-results.title" = "Sonuç yok"; +/* Do not translate - stringsdict entry */ +"autofill.number.of.passwords" = "autofill.number.of.passwords"; + /* Subtitle for prompt to use suggested strong password for creating a login */ "autofill.password-generation-prompt.subtitle" = "Şifreler cihazınızda güvenli bir şekilde saklanır."; diff --git a/DuckDuckGo/tr.lproj/Localizable.stringsdict b/DuckDuckGo/tr.lproj/Localizable.stringsdict index e535be2fb2..65c7351751 100644 --- a/DuckDuckGo/tr.lproj/Localizable.stringsdict +++ b/DuckDuckGo/tr.lproj/Localizable.stringsdict @@ -154,5 +154,107 @@ Onları engelledim! d + autofill.number.of.passwords + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d şifre + other + %1$d şifre + + + autofill.delete.all.passwords.confirmation.title + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Bu %1$d şifreyi silmek istediğinizden emin misiniz? + other + Bu %1$d şifreyi silmek istediğinizden emin misiniz? + + + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Şifreniz bu cihazdan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + other + Şifreniz bu cihazdan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + hesap + other + hesap + + + autofill.delete.all.passwords.sync.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + Şifreleriniz senkronize edilen tüm cihazlardan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + other + Şifreleriniz senkronize edilen tüm cihazlardan silinecektir. %2$#@accounts@ erişimi için başka bir yol olduğundan emin olun. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + hesap + other + hesap + + + autofill.delete.all.passwords.completion + + NSStringLocalizedFormatKey + %#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + one + %1$d şifre silindi + other + %1$d şifre silindi + + diff --git a/DuckDuckGoTests/AutofillLoginListViewModelTests.swift b/DuckDuckGoTests/AutofillLoginListViewModelTests.swift index bf6d6eace3..457445462a 100644 --- a/DuckDuckGoTests/AutofillLoginListViewModelTests.swift +++ b/DuckDuckGoTests/AutofillLoginListViewModelTests.swift @@ -20,18 +20,20 @@ import Foundation import XCTest +import Combine @testable import DuckDuckGo @testable import Core @testable import BrowserServicesKit @testable import Common -// swiftlint:disable line_length +// swiftlint:disable line_length file_length class AutofillLoginListViewModelTests: XCTestCase { private let tld = TLD() private let appSettings = AppUserDefaults() private let vault = (try? MockSecureVaultFactory.makeVault(errorReporter: nil))! private var manager: AutofillNeverPromptWebsitesManager! + private var cancellables: Set = [] override func setUpWithError() throws { try super.setUpWithError() @@ -40,6 +42,7 @@ class AutofillLoginListViewModelTests: XCTestCase { override func tearDownWithError() throws { manager = nil + cancellables.removeAll() try super.tearDownWithError() } @@ -113,6 +116,191 @@ class AutofillLoginListViewModelTests: XCTestCase { XCTAssertEqual(tableContentsToDelete.rowsToDelete.count, 2) } + func testWhenMultipleAccountsSavedAndClearAllThenNoAccountsAreShown() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + } + + func testWhenMultipleAccountsSavedAndClearAllThenUndoThenAccountsAreShownAgain() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + + model.undoClearAllAccounts() + + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + } + + func testWhenOneAccountSavedAndClearAllThenUndoThenAccountIsShownAgain() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 1) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + + model.undoClearAllAccounts() + + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 1) + } + + func testWhenMultipleAccountsSavedAndOneSuggestionAndClearAllThenUndoThenAccountsAndSuggestionsAreShown() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + let testDomain = "testsite.com" + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, currentTabUrl: URL(string: "https://\(testDomain)"), autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 3) + XCTAssertEqual(model.rowsInSection(1), 1) + XCTAssertEqual(model.rowsInSection(2), 3) + + model.clearAllAccounts() + + XCTAssertEqual(model.sections.count, 1) + + model.undoClearAllAccounts() + + XCTAssertEqual(model.sections.count, 3) + XCTAssertEqual(model.rowsInSection(1), 1) + XCTAssertEqual(model.rowsInSection(2), 3) + } + + func testWhenInEditModeThenEnableAutofillSectionIsNotDisplayed() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + + XCTAssertEqual(model.sections.count, 2) + + model.isEditing = true + + XCTAssertEqual(model.sections.count, 1) + } + + func testWhenSearchingThenEnableAutofillSectionIsNotDisplayed() { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + + XCTAssertEqual(model.sections.count, 2) + + model.isSearching = true + model.filterData(with: "z") + + XCTAssertEqual(model.sections.count, 0) + + model.filterData(with: "t") + + XCTAssertEqual(model.sections.count, 1) + } + + func testWhenOneAccountDeletedInEditModeThenAccountsCountPublisherUpdatesCorrectly() { + let expectation = XCTestExpectation(description: "accountsCountPublisher emits an updated count") + + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + ] + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + + model.isEditing = true + model.accountsCountPublisher.sink { count in + XCTAssertEqual(count, model.accountsCount, "The published count should match the number accounts count") + expectation.fulfill() + } + .store(in: &cancellables) + + _ = model.delete(at: IndexPath(row: 1, section: 0)) + wait(for: [expectation], timeout: 1.0) + } + + func testWhenOneAccountSavedAndDeleteAllThenNoAccountsAreShownAndVaultIsEmpty() throws { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()) + ] + for account in vault.storedAccounts { + _ = try vault.storeWebsiteCredentials(SecureVaultModels.WebsiteCredentials(account: account, password: nil)) + } + + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(0), 1) + XCTAssertEqual(model.rowsInSection(1), 1) + XCTAssertEqual(vault.storedAccounts.count, 1) + + model.isEditing = true + let result = model.deleteAllCredentials() + if result { + model.updateData() + } + + XCTAssertEqual(model.sections.count, 0) + XCTAssertEqual(vault.storedAccounts.count, 0) + } + + func testWhenMultipleAccountsSavedAndDeleteAllThenNoAccountsAreShownAndVaultIsEmpty() throws { + vault.storedAccounts = [ + SecureVaultModels.WebsiteAccount(id: "1", title: nil, username: "", domain: "testsite.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "2", title: nil, username: "", domain: "testsite2.com", created: Date(), lastUpdated: Date()), + SecureVaultModels.WebsiteAccount(id: "3", title: nil, username: "", domain: "testsite3.com", created: Date(), lastUpdated: Date()) + ] + for account in vault.storedAccounts { + _ = try vault.storeWebsiteCredentials(SecureVaultModels.WebsiteCredentials(account: account, password: nil)) + } + + let model + = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) + XCTAssertEqual(model.sections.count, 2) + XCTAssertEqual(model.rowsInSection(1), 3) + XCTAssertEqual(vault.storedAccounts.count, 3) + + model.isEditing = true + let result = model.deleteAllCredentials() + if result { + model.updateData() + } + + XCTAssertEqual(model.sections.count, 0) + XCTAssertEqual(vault.storedAccounts.count, 0) + } + func testWhenNoNeverPromptWebsitesSavedThenNeverPromptSectionIsNotShown() { XCTAssertTrue(manager.deleteAllNeverPromptWebsites()) let model = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: vault, autofillNeverPromptWebsitesManager: manager) diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index 9dfc62644f..8545c641fa 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -100,6 +100,11 @@ final class MockSecureVault: AutofillSecureVault { storedCredentials[accountId] = nil } + func deleteAllWebsiteCredentials() throws { + storedCredentials = [:] + storedAccounts = [] + } + func neverPromptWebsites() throws -> [SecureVaultModels.NeverPromptWebsites] { return storedNeverPromptWebsites } @@ -269,6 +274,11 @@ class MockDatabaseProvider: AutofillDatabaseProvider { self._accounts = self._accounts.filter { $0.id != String(accountId) } } + func deleteAllWebsiteCredentials() throws { + self._credentialsDict = [:] + self._accounts = [] + } + func accounts() throws -> [SecureVaultModels.WebsiteAccount] { return _accounts } From 34decedf5e28a0e4aca696474abbfd9e1e3e377d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 5 Mar 2024 19:05:32 +0100 Subject: [PATCH 079/245] 16. Subscription: Display "Activation in progress" message (#2535) Description: Display an "Activation in progress" message if there's an active subscription but no available entitlements. --- DuckDuckGo/SettingsSubscriptionView.swift | 92 +++++++++++++++---- DuckDuckGo/SettingsViewModel.swift | 23 ++++- .../info-16.imageset/Contents.json | 12 +++ .../info-16.imageset/info-16.svg | 11 +++ DuckDuckGo/UserText.swift | 6 +- DuckDuckGo/en.lproj/Localizable.strings | 6 ++ 6 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index c758793012..8dc36fc915 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -31,11 +31,19 @@ struct SettingsSubscriptionView: View { @State var isShowingDBP = false @State var isShowingITP = false + enum Constants { + static let purchaseDescriptionPadding = 5.0 + static let topCellPadding = 3.0 + static let noEntitlementsIconWidth = 20.0 + static let navigationDelay = 0.3 + static let infoIcon = "info-16" + } + private var subscriptionDescriptionView: some View { VStack(alignment: .leading) { Text(UserText.settingsPProSubscribe).daxBodyRegular() Group { - Text(UserText.settingsPProDescription).daxFootnoteRegular().padding(.bottom, 5) + Text(UserText.settingsPProDescription).daxFootnoteRegular().padding(.bottom, Constants.purchaseDescriptionPadding) Text(UserText.settingsPProFeatures).daxFootnoteRegular() }.foregroundColor(Color(designSystemColor: .textSecondary)) } @@ -53,20 +61,38 @@ struct SettingsSubscriptionView: View { .foregroundColor(Color.init(designSystemColor: .accent)) } + @ViewBuilder + private var restorePurchaseView: some View { + let text = !viewModel.isRestoringSubscription ? UserText.subscriptionActivateAppleIDButton : UserText.subscriptionRestoringTitle + SettingsCustomCell(content: { + Text(text) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent)) }, + action: { + Task { await viewModel.restoreAccountPurchase() } + }, + isButton: !viewModel.isRestoringSubscription ) + .alert(isPresented: $viewModel.shouldDisplayRestoreSubscriptionError) { + Alert( + title: Text(UserText.subscriptionAppStoreErrorTitle), + message: Text(UserText.subscriptionAppStoreErrorMessage), + dismissButton: .default(Text(UserText.actionOK)) {} + ) + } + } + private var manageSubscriptionView: some View { Text(UserText.settingsPProManageSubscription) .daxBodyRegular() } - + + @ViewBuilder private var purchaseSubscriptionView: some View { - return Group { + Group { SettingsCustomCell(content: { subscriptionDescriptionView }) SettingsCustomCell(content: { learnMoreView }, action: { isShowingsubScriptionFlow = true }, isButton: true ) - .sheet(isPresented: $isShowingsubScriptionFlow) { - SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() - } SettingsCustomCell(content: { iHaveASubscriptionView }, action: { @@ -78,8 +104,28 @@ struct SettingsSubscriptionView: View { } } + @ViewBuilder + private var noEntitlementsAvailableView: some View { + Group { + SettingsCustomCell(content: { + HStack(alignment: .top) { + Image(Constants.infoIcon) + .frame(width: Constants.noEntitlementsIconWidth) + .padding(.top, Constants.topCellPadding) + VStack(alignment: .leading) { + Text(UserText.settingsPProActivationPendingTitle).daxBodyRegular() + Text(UserText.settingsPProActivationPendingDescription).daxFootnoteRegular() + .padding(.bottom, Constants.purchaseDescriptionPadding) + }.foregroundColor(Color(designSystemColor: .textSecondary)) + } + }) + restorePurchaseView + } + } + + @ViewBuilder private var subscriptionDetailsView: some View { - return Group { + Group { if viewModel.shouldShowNetP { SettingsCellView(label: UserText.settingsPProVPNTitle, subtitle: viewModel.state.networkProtection.status != "" ? viewModel.state.networkProtection.status : nil, @@ -105,13 +151,11 @@ struct SettingsSubscriptionView: View { SubscriptionITPView() } } - - if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { - NavigationLink(destination: SubscriptionSettingsView()) { - SettingsCustomCell(content: { manageSubscriptionView }) - } + + NavigationLink(destination: SubscriptionSettingsView()) { + SettingsCustomCell(content: { manageSubscriptionView }) } - + } } @@ -119,12 +163,28 @@ struct SettingsSubscriptionView: View { if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { if viewModel.state.subscription.hasActiveSubscription { - subscriptionDetailsView + + // Allow managing the subscription if we have some entitlements + if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { + subscriptionDetailsView + + // If no entitlements it should mean the backend is still out of sync + } else { + noEntitlementsAvailableView + } + } else { purchaseSubscriptionView + } } + // Subscription Restore + .sheet(isPresented: $isShowingsubScriptionFlow) { + SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() + } + + // Refresh subscription when dismissing the Subscription Flow .onChange(of: isShowingsubScriptionFlow, perform: { value in if !value { @@ -135,7 +195,7 @@ struct SettingsSubscriptionView: View { .onChange(of: viewModel.shouldNavigateToDBP, perform: { value in if value { // Allow the sheet to dismiss before presenting a new one - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.navigationDelay) { isShowingDBP = true } } @@ -144,7 +204,7 @@ struct SettingsSubscriptionView: View { .onChange(of: viewModel.shouldNavigateToITP, perform: { value in if value { // Allow the sheet to dismiss before presenting a new one - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3) { + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.navigationDelay) { isShowingITP = true } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index e2aa84904e..a4e386c80b 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -50,6 +50,8 @@ final class SettingsViewModel: ObservableObject { #if SUBSCRIPTION private var accountManager: AccountManager private var signOutObserver: Any? + @Published var isRestoringSubscription: Bool = false + @Published var shouldDisplayRestoreSubscriptionError: Bool = false #endif @@ -405,7 +407,26 @@ extension SettingsViewModel { } } - #endif + @available(iOS 15.0, *) + func restoreAccountPurchase() async { + DispatchQueue.main.async { self.isRestoringSubscription = true } + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + switch result { + case .success: + DispatchQueue.main.async { + self.isRestoringSubscription = false + } + await self.setupSubscriptionEnvironment() + + case .failure: + DispatchQueue.main.async { + self.isRestoringSubscription = false + self.shouldDisplayRestoreSubscriptionError = true + } + } + } + +#endif // SUBSCRIPTION #if NETWORK_PROTECTION private func updateNetPStatus(connectionStatus: ConnectionStatus) { diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json new file mode 100644 index 0000000000..6e2e5f1256 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "info-16.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg new file mode 100644 index 0000000000..b9390be066 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/info-16.imageset/info-16.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 8a769e5c59..bf53406e72 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -999,7 +999,11 @@ But if you *do* want a peek under the hood, you can find more information about public static let settingsPProDBPSubTitle = NSLocalizedString("settings.subscription.DBP.subtitle", value: "Remove your info from sites that sell it", comment: "Data Broker protection cell subtitle for privacy pro") public static let settingsPProITRTitle = NSLocalizedString("settings.subscription.ITR.title", value: "Identity Theft Restoration", comment: "Identity theft restoration cell title for privacy pro") public static let settingsPProITRSubTitle = NSLocalizedString("settings.subscription.ITR.subtitle", value: "If your identity is stolen, we'll help restore it", comment: "Identity theft restoration cell subtitle for privacy pro") - + + public static let settingsPProActivationPendingTitle = NSLocalizedString("settings.subscription.activation.pending.title", value: "Your Subscription is Being Activated", comment: "Subscription activation pending title") + public static let settingsPProActivationPendingDescription = NSLocalizedString("settings.subscription.activation.pending.description", value: "This is taking longer than usual, please check back later.", comment: "Subscription activation pending description") + + // Customize Section public static let settingsCustomizeSection = NSLocalizedString("settings.customize", value: "Customize", comment: "Settings title for the customize section") public static let settingsKeyboard = NSLocalizedString("settings.keyboard", value: "Keyboard", comment: "Settings screen cell for Keyboard") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 4459952b4f..7025259c86 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1855,6 +1855,12 @@ But if you *do* want a peek under the hood, you can find more information about /* Settings title for the privacy section */ "settings.privacy" = "Privacy"; +/* Subscription activation pending description */ +"settings.subscription.activation.pending.description" = "This is taking longer than usual, please check back later."; + +/* Subscription activation pending title */ +"settings.subscription.activation.pending.title" = "Your Subscription is Being Activated"; + /* Data Broker protection cell subtitle for privacy pro */ "settings.subscription.DBP.subtitle" = "Remove your info from sites that sell it"; From d9e49af1b429677f43357564d893d32b9a6f2fc3 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 6 Mar 2024 11:57:04 +0100 Subject: [PATCH 080/245] Makes dbSaveBloomFilterError daily and count (#2526) Task/Issue URL: https://app.asana.com/0/1199230911884351/1205962557569232/f macOS PR: https://github.com/duckduckgo/macos-browser/pull/2299 ## Description We're making the `dbSaveBloomFilterError` pixel fire daily and continuously to be able to better understand impact (ie: number of users affected). Also fixes an issue in the debug menu that was causing the privacy config to be refreshed an ever-increasing number of times. --- Core/PrivacyFeatures.swift | 10 +++++++--- DuckDuckGo/ConfigurationURLDebugViewController.swift | 12 +++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/Core/PrivacyFeatures.swift b/Core/PrivacyFeatures.swift index b3ca70c839..977a5d94f9 100644 --- a/Core/PrivacyFeatures.swift +++ b/Core/PrivacyFeatures.swift @@ -33,17 +33,21 @@ public final class PrivacyFeatures { } private static let httpsUpgradeDebugEvents = EventMapping { event, error, parameters, onComplete in let domainEvent: Pixel.Event + let dailyAndCount: Bool + switch event { case .dbSaveBloomFilterError: domainEvent = .dbSaveBloomFilterError + dailyAndCount = true case .dbSaveExcludedHTTPSDomainsError: domainEvent = .dbSaveExcludedHTTPSDomainsError + dailyAndCount = false } - if let error { - Pixel.fire(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + if dailyAndCount { + DailyPixel.fireDailyAndCount(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onCountComplete: onComplete) } else { - Pixel.fire(pixel: domainEvent, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) + Pixel.fire(pixel: domainEvent, error: error, withAdditionalParameters: parameters ?? [:], onComplete: onComplete) } } private static var httpsUpgradeStore: AppHTTPSUpgradeStore { diff --git a/DuckDuckGo/ConfigurationURLDebugViewController.swift b/DuckDuckGo/ConfigurationURLDebugViewController.swift index f508a40393..7a6e970cc4 100644 --- a/DuckDuckGo/ConfigurationURLDebugViewController.swift +++ b/DuckDuckGo/ConfigurationURLDebugViewController.swift @@ -127,7 +127,7 @@ final class ConfigurationURLDebugViewController: UITableViewController { cell.subtitle.text = url(for: row) cell.subtitle.textColor = customURL(for: row) != nil ? UIColor(designSystemColor: .accent) : .black cell.ternary.text = lastConfigurationUpdateDate != nil ? dateFormatter.string(from: lastConfigurationUpdateDate!) : "-" - cell.refresh.addAction(makeAction(for: row), for: .allEvents) + cell.refresh.addAction(refreshAction, for: .primaryActionTriggered) return cell } @@ -136,12 +136,10 @@ final class ConfigurationURLDebugViewController: UITableViewController { presentCustomURLAlert(for: row) } - private func makeAction(for row: CustomURLsRows) -> UIAction { - UIAction { [weak self] _ in - self?.lastConfigurationRefreshDate = Date.distantPast - self?.fetchAssets() - self?.tableView.reloadData() - } + private lazy var refreshAction = UIAction { [weak self] _ in + self?.lastConfigurationRefreshDate = Date.distantPast + self?.fetchAssets() + self?.tableView.reloadData() } private func presentCustomURLAlert(for row: CustomURLsRows) { From d6b7c9791701a1a3c4535d4ffe0e93d94732ae65 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 6 Mar 2024 16:27:51 +0100 Subject: [PATCH 081/245] Integrate confirm entitlements endpoint (#2541) --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +---- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/SettingsViewModel.swift | 4 ++-- ...IdentityTheftRestorationPagesFeature.swift | 3 ++- .../UserScripts/Subscription.swift | 24 ------------------- ...scriptionPagesUseSubscriptionFeature.swift | 18 +++++++++----- .../SubscriptionSettingsViewModel.swift | 18 +++++++------- .../SubscriptionDebugViewController.swift | 2 +- 8 files changed, 28 insertions(+), 51 deletions(-) delete mode 100644 DuckDuckGo/Subscription/UserScripts/Subscription.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 074ad901eb..2a8d4acacc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -799,7 +799,6 @@ D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */; }; D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; }; D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */; }; - D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92C2B696945008E2FF2 /* Subscription.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; @@ -2464,7 +2463,6 @@ D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionITPViewModel.swift; sourceTree = ""; }; D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesFeature.swift; sourceTree = ""; }; - D668D92C2B696945008E2FF2 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; @@ -4641,7 +4639,6 @@ D664C7B02B289AA000CBFA76 /* UserScripts */ = { isa = PBXGroup; children = ( - D668D92C2B696945008E2FF2 /* Subscription.swift */, D664C7B32B289AA000CBFA76 /* SubscriptionPagesUserScript.swift */, D664C7B52B289AA000CBFA76 /* SubscriptionPagesUseSubscriptionFeature.swift */, D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */, @@ -6939,7 +6936,6 @@ EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, C18ED43A2AB6F77600BF3805 /* AutofillSettingsEnableFooterView.swift in Sources */, CB84C7BD29A3EF530088A5B8 /* AppConfigurationURLProvider.swift in Sources */, - D668D92D2B696945008E2FF2 /* Subscription.swift in Sources */, AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */, 316931D927BD22A80095F5ED /* DownloadActionMessageViewHelper.swift in Sources */, 9838059F2228208E00385F1A /* PositiveFeedbackViewController.swift in Sources */, @@ -10001,7 +9997,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 114.3.0; + version = 115.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 5f6b2bab9d..d879edc585 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "1fa06fb43fb0f26fc1a74b710fb1402573264bf5", - "version" : "114.3.0" + "revision" : "06ba37e23cd4c8d848d9d153c6e5be07956d3adc", + "version" : "115.0.0" } }, { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index a4e386c80b..b94ef3a23a 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -348,9 +348,9 @@ extension SettingsViewModel { } // Fetch available subscriptions from the backend (or sign out) - switch await SubscriptionService.getSubscriptionDetails(token: token) { + switch await SubscriptionService.getSubscription(accessToken: token) { - case .success(let response) where response.isSubscriptionActive: + case .success(let subscription) where subscription.isActive: // Cache Subscription state cacheSubscriptionState(active: true) diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 055e3bc1cf..276ef14926 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -32,6 +32,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { struct Constants { static let featureName = "useIdentityTheftRestoration" static let os = "ios" + static let token = "token" } struct OriginDomains { @@ -68,7 +69,7 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { let authToken = AccountManager().authToken ?? "" - return Subscription(token: authToken) + return [Constants.token: authToken] } deinit { diff --git a/DuckDuckGo/Subscription/UserScripts/Subscription.swift b/DuckDuckGo/Subscription/UserScripts/Subscription.swift deleted file mode 100644 index 9306a531c5..0000000000 --- a/DuckDuckGo/Subscription/UserScripts/Subscription.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// Subscription.swift -// DuckDuckGo -// -// 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 Subscription: Encodable { - let token: String -} diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 0af6a2d15f..9cd2090506 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -30,6 +30,8 @@ enum SubscriptionTransactionStatus { case idle, purchasing, restoring, polling } +// swiftlint:disable type_body_length + @available(iOS 15.0, *) final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObject { @@ -37,6 +39,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec static let featureName = "useSubscription" static let os = "ios" static let empty = "" + static let token = "token" } struct OriginDomains { @@ -156,7 +159,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // MARK: Broker Methods (Called from WebView via UserScripts) func getSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { let authToken = AccountManager().authToken ?? Constants.empty - return Subscription(token: authToken) + return [Constants.token: authToken] } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { @@ -202,10 +205,11 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } let emailAccessToken = try? EmailManager().getToken() + let purchaseTransactionJWS: String switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { - case .success: - break + case .success(let transactionJWS): + purchaseTransactionJWS = transactionJWS case .failure(let error): switch error { @@ -220,7 +224,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } setTransactionStatus(.polling) - switch await AppStorePurchaseFlow.completeSubscriptionPurchase() { + switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { case .success(let purchaseUpdate): await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure: @@ -256,10 +260,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let accountManager = AccountManager() if let accessToken = accountManager.accessToken, case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - switch await SubscriptionService.getSubscriptionDetails(token: accessToken) { + switch await SubscriptionService.getSubscription(accessToken: accessToken) { // If the account is not active, display an error and logout - case .success(let response) where !response.isSubscriptionActive: + case .success(let subscription) where !subscription.isActive: setTransactionError(.failedToRestoreFromEmailSubscriptionInactive) accountManager.signOut() return nil @@ -353,4 +357,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } +// swiftlint:enable type_body_length + #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 6ad599d49f..f52e6f75c4 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -27,8 +27,6 @@ import Subscription final class SubscriptionSettingsViewModel: ObservableObject { enum Constants { - static let autoRenewable = "Auto-Renewable" - static let notAutoRenewable = "Not Auto-Renewable" static let monthlyProductID = "ios.subscription.1month" static let yearlyProductID = "ios.subscription.1year" static let updateFrequency: Float = 10 @@ -61,19 +59,19 @@ final class SubscriptionSettingsViewModel: ObservableObject { Task { guard let token = accountManager.accessToken else { return } - if let cachedDate = SubscriptionService.cachedSubscriptionDetailsResponse?.expiresOrRenewsAt, - let cachedStatus = SubscriptionService.cachedSubscriptionDetailsResponse?.status, - let productID = SubscriptionService.cachedSubscriptionDetailsResponse?.productId { + if let cachedDate = SubscriptionService.cachedGetSubscriptionResponse?.expiresOrRenewsAt, + let cachedStatus = SubscriptionService.cachedGetSubscriptionResponse?.status, + let productID = SubscriptionService.cachedGetSubscriptionResponse?.productId { updateSubscriptionDetails(status: cachedStatus, date: cachedDate, product: productID) } - if case .success(let response) = await SubscriptionService.getSubscriptionDetails(token: token) { - if !response.isSubscriptionActive { + if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token) { + if !subscription.isActive { AccountManager().signOut() shouldDismissView = true return } else { - updateSubscriptionDetails(status: response.status, date: response.expiresOrRenewsAt, product: response.productId) + updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId) } } } @@ -97,8 +95,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { } - private func updateSubscriptionDetails(status: String, date: Date, product: String) { - let statusString = (status == Self.Constants.autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires + private func updateSubscriptionDetails(status: Subscription.Status, date: Date, product: String) { + let statusString = (status == .autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires self.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) self.subscriptionType = product == Constants.monthlyProductID ? UserText.subscriptionMonthly : UserText.subscriptionAnnual } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index feb61f2310..27c4b67c96 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -222,7 +222,7 @@ final class SubscriptionDebugViewController: UITableViewController { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } - switch await SubscriptionService.getSubscriptionDetails(token: token) { + switch await SubscriptionService.getSubscription(accessToken: token) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): From 52011471417f75d52d024f5a8c92ada880665dbf Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Wed, 6 Mar 2024 15:47:08 +0000 Subject: [PATCH 082/245] fix opening tabs with transitional (#2542) Task/Issue URL: https://app.asana.com/0/414235014887631/1206769708570992/f Tech Design URL: CC: Description: Pages that open new tabs in a certain way will get stuck on about:blank Steps to test this PR: Visit https://finance.yahoo.com/news/ocean-biomedical-inc-announces-publication-131500736.html?guccounter=1 and get to the article content Click on a link in the article. A new tab should open and navigate to the content Visit https://chrisbrind.rocks and click on one of the Connect buttons (instagram, tiktok, mastodon) - should all open in a new tab and load successfully (I know this website is awful sorry) --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/MainViewController.swift | 3 ++- DuckDuckGo/TabManager.swift | 9 +++++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d879edc585..dffa9b0106 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index d125eaccaf..41df7f48c2 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1833,7 +1833,8 @@ extension MainViewController: TabDelegate { showBars() currentTab?.dismiss() - let newTab = tabManager.addURLRequest(navigationAction.request, + // Don't use a request or else the page gets stuck on "about:blank" + let newTab = tabManager.addURLRequest(nil, with: configuration, inheritedAttribution: inheritingAttribution) newTab.openedByPage = true diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 39e2629895..68a8fa473a 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -120,7 +120,7 @@ class TabManager { return current(createIfNeeded: true)! } - func addURLRequest(_ request: URLRequest, + func addURLRequest(_ request: URLRequest?, with configuration: WKWebViewConfiguration, inheritedAttribution: AdClickAttributionLogic.State?) -> TabViewController { @@ -128,7 +128,12 @@ class TabManager { fatalError("Failed to copy configuration") } - let tab = Tab(link: request.url == nil ? nil : Link(title: nil, url: request.url!)) + let tab: Tab + if let request { + tab = Tab(link: request.url == nil ? nil : Link(title: nil, url: request.url!)) + } else { + tab = Tab() + } model.insert(tab: tab, at: model.currentIndex + 1) model.select(tabAt: model.currentIndex + 1) From 21ce049b4f1efe0cc1aaa7f39d9f3e00f1a7c31d Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:15:28 -0500 Subject: [PATCH 083/245] Add preliminary support for subscription keychain sharing (#2538) Co-authored-by: Graeme Arthur --- Configuration/Configuration-Alpha.xcconfig | 3 ++ Configuration/DuckDuckGoDeveloper.xcconfig | 3 ++ Core/AccountManagerExtension.swift | 31 ++++++++++++++ Core/BundleExtension.swift | 40 +++++++++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 22 +++++++++- .../xcshareddata/swiftpm/Package.resolved | 6 +-- DuckDuckGo/DuckDuckGo.entitlements | 9 ++++- DuckDuckGo/DuckDuckGoAlpha.entitlements | 5 +++ DuckDuckGo/Info.plist | 2 + DuckDuckGo/SettingsViewModel.swift | 2 +- ...scriptionPagesUseSubscriptionFeature.swift | 11 +++-- PacketTunnelProvider/Info.plist | 2 + ...etworkProtectionPacketTunnelProvider.swift | 6 ++- .../PacketTunnelProvider.entitlements | 5 +++ .../PacketTunnelProviderAlpha.entitlements | 5 +++ 15 files changed, 140 insertions(+), 12 deletions(-) create mode 100644 Core/AccountManagerExtension.swift create mode 100644 Core/BundleExtension.swift diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig index 4c15890703..940e9dfdcf 100644 --- a/Configuration/Configuration-Alpha.xcconfig +++ b/Configuration/Configuration-Alpha.xcconfig @@ -26,3 +26,6 @@ APP_ID = com.duckduckgo.mobile.ios.alpha // A prefix for group ids. Must start with "group.". GROUP_ID_PREFIX = group.com.duckduckgo.alpha + +// The keychain access group for subscriptions +SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions.alpha diff --git a/Configuration/DuckDuckGoDeveloper.xcconfig b/Configuration/DuckDuckGoDeveloper.xcconfig index d23a9aaa9c..37195c89c5 100644 --- a/Configuration/DuckDuckGoDeveloper.xcconfig +++ b/Configuration/DuckDuckGoDeveloper.xcconfig @@ -27,3 +27,6 @@ CODE_SIGN_STYLE = Manual // The manually specified provisioning profile PROVISIONING_PROFILE_SPECIFIER = Development - App + +// The keychain access group for subscriptions +SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions diff --git a/Core/AccountManagerExtension.swift b/Core/AccountManagerExtension.swift new file mode 100644 index 0000000000..5dd738fa3d --- /dev/null +++ b/Core/AccountManagerExtension.swift @@ -0,0 +1,31 @@ +// +// AccountManagerExtension.swift +// DuckDuckGo +// +// 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. +// + +#if SUBSCRIPTION + +import Foundation +import Subscription + +public extension AccountManager { + convenience init() { + self.init(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + } +} + +#endif diff --git a/Core/BundleExtension.swift b/Core/BundleExtension.swift new file mode 100644 index 0000000000..97b82eaae5 --- /dev/null +++ b/Core/BundleExtension.swift @@ -0,0 +1,40 @@ +// +// BundleExtension.swift +// DuckDuckGo +// +// 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 Bundle { + public func appGroup(bundle: BundleGroup) -> String { + var appGroupName: String + + switch bundle { + case .subs: + appGroupName = "SUBSCRIPTION_APP_GROUP" + } + + guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { + fatalError("Info.plist is missing \(appGroupName)") + } + return appGroup + } +} + +public enum BundleGroup { + case subs +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2a8d4acacc..03e362adc5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -695,11 +695,15 @@ B6BA95C528894A28004ABA20 /* BrowsingMenuViewController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */; }; B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6BA95E728924730004ABA20 /* JSAlertController.storyboard */; }; B6CB93E5286445AB0090FEB4 /* Base64DownloadSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */; }; + BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD15DB842B959CFD00821457 /* BundleExtension.swift */; }; BD862E032B30DA170073E2EE /* VPNFeedbackFormViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */; }; BD862E052B30DB250073E2EE /* VPNFeedbackCategory.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */; }; BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */; }; BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */; }; BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */; }; + BDA583872B98B6C700732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; + BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; + BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; @@ -2344,11 +2348,13 @@ B6BA95C428894A28004ABA20 /* BrowsingMenuViewController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = BrowsingMenuViewController.storyboard; sourceTree = ""; }; B6BA95E728924730004ABA20 /* JSAlertController.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = JSAlertController.storyboard; sourceTree = ""; }; B6CB93E4286445AB0090FEB4 /* Base64DownloadSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base64DownloadSession.swift; sourceTree = ""; }; + BD15DB842B959CFD00821457 /* BundleExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtension.swift; sourceTree = ""; }; BD862E022B30DA170073E2EE /* VPNFeedbackFormViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormViewModel.swift; sourceTree = ""; }; BD862E042B30DB250073E2EE /* VPNFeedbackCategory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackCategory.swift; sourceTree = ""; }; BD862E062B30F5E30073E2EE /* VPNFeedbackSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackSender.swift; sourceTree = ""; }; BD862E082B30F63E0073E2EE /* VPNMetadataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNMetadataCollector.swift; sourceTree = ""; }; BD862E0A2B30F9300073E2EE /* VPNFeedbackFormView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNFeedbackFormView.swift; sourceTree = ""; }; + BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountManagerExtension.swift; sourceTree = ""; }; BDC234F62B27F51100D3C798 /* UniquePixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniquePixel.swift; sourceTree = ""; }; C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutofillViews.swift; sourceTree = ""; }; C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarkOrFolderTests.swift; sourceTree = ""; }; @@ -4412,6 +4418,15 @@ path = Feedback; sourceTree = ""; }; + BDA583852B98B69C00732FDC /* Subscription */ = { + isa = PBXGroup; + children = ( + BD15DB842B959CFD00821457 /* BundleExtension.swift */, + BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */, + ); + name = Subscription; + sourceTree = ""; + }; C14882D627F2010700D59F0C /* ImportExport */ = { isa = PBXGroup; children = ( @@ -5104,6 +5119,7 @@ F143C2E51E4A4CD400CFDE3A /* Core */ = { isa = PBXGroup; children = ( + BDA583852B98B69C00732FDC /* Subscription */, 858479C72B8792C900D156C1 /* History */, EE7A92852AC6DE2500832A36 /* NetworkProtection */, 4B470ED4299C484B0086EBDC /* AppTrackingProtection */, @@ -6465,6 +6481,7 @@ 02025AD42988229800E694E7 /* ProxySocket.swift in Sources */, 02025AD62988229800E694E7 /* SocketProtocol.swift in Sources */, 02025AD82988229800E694E7 /* Tunnel.swift in Sources */, + BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */, 02025ADA2988229800E694E7 /* Port.swift in Sources */, 02025ADB2988229800E694E7 /* HTTPStreamScanner.swift in Sources */, 02025ADC2988229800E694E7 /* UInt128.swift in Sources */, @@ -6895,6 +6912,7 @@ 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, + BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */, C13F3F6C2B7F88470083BE40 /* AuthConfirmationPromptViewModel.swift in Sources */, 1EEF124E2850EADE003DDE57 /* PrivacyIconView.swift in Sources */, 37FCAAAB29911BF1000E420A /* WaitlistExtensions.swift in Sources */, @@ -7251,6 +7269,7 @@ 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */, F1134EB01F40AC6300B73467 /* AtbParser.swift in Sources */, EE50052E29C369D300AE0773 /* FeatureFlag.swift in Sources */, + BD15DB852B959CFD00821457 /* BundleExtension.swift in Sources */, 37DF000F29F9D635002B7D3E /* SyncBookmarksAdapter.swift in Sources */, B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */, 4BE2756827304F57006B20B0 /* URLRequestExtension.swift in Sources */, @@ -7291,6 +7310,7 @@ B652DF0D287C2A6300C12A9C /* PrivacyFeatures.swift in Sources */, F10E522D1E946F8800CE1253 /* NSAttributedStringExtension.swift in Sources */, 9887DC252354D2AA005C85F5 /* Database.swift in Sources */, + BDA583872B98B6C700732FDC /* AccountManagerExtension.swift in Sources */, F143C3171E4A99D200CFDE3A /* AppURLs.swift in Sources */, C1963863283794A000298D4D /* BookmarksCachingSearch.swift in Sources */, ); @@ -9997,7 +10017,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 115.0.0; + version = 116.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dffa9b0106..3abe889c31 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "06ba37e23cd4c8d848d9d153c6e5be07956d3adc", - "version" : "115.0.0" + "revision" : "5a6c7b62f84e7c2a4ffa7803392284a055229aef", + "version" : "116.0.0" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/DuckDuckGo.entitlements b/DuckDuckGo/DuckDuckGo.entitlements index 82bd4ed6cd..0133866cab 100644 --- a/DuckDuckGo/DuckDuckGo.entitlements +++ b/DuckDuckGo/DuckDuckGo.entitlements @@ -2,12 +2,12 @@ - com.apple.developer.web-browser - com.apple.developer.networking.networkextension packet-tunnel-provider + com.apple.developer.web-browser + com.apple.security.application-groups $(GROUP_ID_PREFIX).bookmarks @@ -17,5 +17,10 @@ $(GROUP_ID_PREFIX).apptp $(GROUP_ID_PREFIX).netp + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + diff --git a/DuckDuckGo/DuckDuckGoAlpha.entitlements b/DuckDuckGo/DuckDuckGoAlpha.entitlements index 0a73901384..c9b90b996b 100644 --- a/DuckDuckGo/DuckDuckGoAlpha.entitlements +++ b/DuckDuckGo/DuckDuckGoAlpha.entitlements @@ -17,5 +17,10 @@ group.com.duckduckgo.alpha.netp group.com.duckduckgo.alpha.statistics + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index b61e407988..e5d5328d08 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -216,6 +216,8 @@ UIStatusBarHidden + SUBSCRIPTION_APP_GROUP + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) UIStatusBarStyle UIStatusBarStyleDefault UISupportedInterfaceOrientations~ipad diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index b94ef3a23a..30f48b19de 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -410,7 +410,7 @@ extension SettingsViewModel { @available(iOS 15.0, *) func restoreAccountPurchase() async { DispatchQueue.main.async { self.isRestoringSubscription = true } - let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: DispatchQueue.main.async { diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 9cd2090506..98fd019c13 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -207,9 +207,12 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec let emailAccessToken = try? EmailManager().getToken() let purchaseTransactionJWS: String - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, emailAccessToken: emailAccessToken) { + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, + emailAccessToken: emailAccessToken, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { case .success(let transactionJWS): purchaseTransactionJWS = transactionJWS + case .failure(let error): switch error { @@ -224,7 +227,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } setTransactionStatus(.polling) - switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS) { + switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { case .success(let purchaseUpdate): await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) case .failure: @@ -323,7 +327,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func restoreAccountFromAppStorePurchase() async throws { setTransactionStatus(.restoring) - let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase() + let result = await AppStoreRestoreFlow.restoreAccountFromPastPurchase(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) switch result { case .success: setTransactionStatus(.idle) @@ -356,7 +360,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } } - // swiftlint:enable type_body_length #endif diff --git a/PacketTunnelProvider/Info.plist b/PacketTunnelProvider/Info.plist index b9e2818ff1..8d86253eb9 100644 --- a/PacketTunnelProvider/Info.plist +++ b/PacketTunnelProvider/Info.plist @@ -4,6 +4,8 @@ DuckDuckGoGroupIdentifierPrefix $(GROUP_ID_PREFIX) + SUBSCRIPTION_APP_GROUP + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) NSExtension NSExtensionPointIdentifier diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index f9e7fa92f7..408e9c6015 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -277,7 +277,11 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } private static func entitlementCheck() async -> Result { - let result = await AccountManager().hasEntitlement(for: .networkProtection) +#if ALPHA + SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging +#endif + + let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).hasEntitlement(for: .networkProtection) switch result { case .success(let hasEntitlement): return .success(hasEntitlement) diff --git a/PacketTunnelProvider/PacketTunnelProvider.entitlements b/PacketTunnelProvider/PacketTunnelProvider.entitlements index 5e171bb76b..d4e6617d26 100644 --- a/PacketTunnelProvider/PacketTunnelProvider.entitlements +++ b/PacketTunnelProvider/PacketTunnelProvider.entitlements @@ -11,5 +11,10 @@ $(GROUP_ID_PREFIX).apptp $(GROUP_ID_PREFIX).netp + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + diff --git a/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements b/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements index f814f005e7..a346d69b16 100644 --- a/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements +++ b/PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements @@ -11,5 +11,10 @@ group.com.duckduckgo.alpha.apptp group.com.duckduckgo.alpha.netp + keychain-access-groups + + $(AppIdentifierPrefix)$(APP_ID) + $(AppIdentifierPrefix)$(SUBSCRIPTION_APP_GROUP) + From 28c56c6d874b20250a84038fbbf8fbbd001a2a86 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 6 Mar 2024 12:58:46 -0800 Subject: [PATCH 084/245] Remove CGNAT range (#2524) Task/Issue URL: https://app.asana.com/0/414235014887631/1206718079780271/f Tech Design URL: CC: Description: Client PR for duckduckgo/BrowserServicesKit#691 --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 03e362adc5..0f50b88fa0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10017,7 +10017,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.0.0; + version = 116.0.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3abe889c31..f454fcbcfa 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "5a6c7b62f84e7c2a4ffa7803392284a055229aef", - "version" : "116.0.0" + "revision" : "12b960a8cc18cf080c41422ac7913e1c0cd9d874", + "version" : "116.0.1" } }, { @@ -165,7 +165,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" From 5405554cdf723b21ddae9abb08c639568e9fbfdf Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Wed, 6 Mar 2024 22:31:21 +0100 Subject: [PATCH 085/245] Validate correct environment (#2546) Task/Issue URL: https://app.asana.com/0/0/1206645876801807/f Tech Design URL: CC: Description: Fire Unique pixel in case we are on dev environment but we most likely should not be. --- Core/PixelEvent.swift | 3 +++ DuckDuckGo/AppDelegate.swift | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 527ce9c495..be65c36f6a 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -499,6 +499,7 @@ extension Pixel { case syncRemoveDeviceError case syncDeleteAccountError case syncLoginExistingAccountError + case syncWrongEnvironment case swipeTabsUsed case swipeTabsUsedDaily @@ -1004,6 +1005,8 @@ extension Pixel.Event { case .syncDeleteAccountError: return "m_d_sync_delete_account_error" case .syncLoginExistingAccountError: return "m_d_sync_login_existing_account_error" + case .syncWrongEnvironment: return "m_d_sync_wrong_environment_u" + case .swipeTabsUsed: return "m_swipe-tabs-used" case .swipeTabsUsedDaily: return "m_swipe-tabs-used-daily" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 3bd12868ed..72323da1e2 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -399,6 +399,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { guard !testing else { return } syncService.initializeIfNeeded() + if syncService.authState == .active && + (InternalUserStore().isInternalUser == false && syncService.serverEnvironment == .development) { + UniquePixel.fire(pixel: .syncWrongEnvironment) + } syncDataProviders.setUpDatabaseCleanersIfNeeded(syncService: syncService) if !(overlayWindow?.rootViewController is AuthenticationViewController) { From 68151f84886e7dbb0a156655bb47619d04028734 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:08:40 -0500 Subject: [PATCH 086/245] Remove isSubscriptionEnabled check when attempting to delete NetP token (#2548) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0f50b88fa0..6600d13023 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10017,7 +10017,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.0.1; + version = 116.0.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f454fcbcfa..141daf2b93 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "12b960a8cc18cf080c41422ac7913e1c0cd9d874", - "version" : "116.0.1" + "revision" : "5b354c6c91dc6059afd9b6f2b8f2b212d5b50046", + "version" : "116.0.2" } }, { From da0450e9052f2c868ebd942655402d8520d382d5 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 6 Mar 2024 23:18:00 -0500 Subject: [PATCH 087/245] Handle expired entitlement in NetP (#2525) Task/Issue URL: https://app.asana.com/0/0/1206409081785857/f Description: This PR adds proper entitlement expiration handling to NetP. Steps to test this PR: Go through the subscription flow With the VPN on, wait till the subscription expires. You should encounter the messaging & tunnel shutdown Resubscribe. Keep the VPN off. Once the subscription expires, try to reconnect. Again you should see the messaging. --- Core/BundleExtensions.swift | 40 +++++++++++++ DuckDuckGo.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 32 ++++++++--- DuckDuckGo/CriticalAlerts.swift | 9 +-- DuckDuckGo/MainViewController+Segues.swift | 8 +++ DuckDuckGo/MainViewController.swift | 56 +++++++++++++++++-- ...NetworkProtectionDebugViewController.swift | 10 ++++ DuckDuckGo/SettingsSubscriptionView.swift | 8 ++- DuckDuckGo/SettingsViewModel.swift | 7 ++- DuckDuckGo/UserText.swift | 14 +++++ DuckDuckGo/en.lproj/Localizable.strings | 18 ++++++ ...etworkProtectionPacketTunnelProvider.swift | 2 + ...orkProtectionUNNotificationPresenter.swift | 6 +- 14 files changed, 192 insertions(+), 28 deletions(-) create mode 100644 Core/BundleExtensions.swift diff --git a/Core/BundleExtensions.swift b/Core/BundleExtensions.swift new file mode 100644 index 0000000000..2ba420193f --- /dev/null +++ b/Core/BundleExtensions.swift @@ -0,0 +1,40 @@ +// +// BundleExtensions.swift +// DuckDuckGo +// +// 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 Bundle { + public func appGroup(bundle: BundleGroup) -> String { + var appGroupName: String + + switch bundle { + case .subs: + appGroupName = "SUBSCRIPTION_APP_GROUP" + } + + guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { + fatalError("Info.plist is missing \(appGroupName)") + } + return appGroup + } +} + +public enum BundleGroup { + case subs +} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 6600d13023..ff266ce02e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -705,6 +705,7 @@ BDA583882B98B92F00732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDA583892B98BA7600732FDC /* AccountManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */; }; BDC234F72B27F51100D3C798 /* UniquePixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC234F62B27F51100D3C798 /* UniquePixel.swift */; }; + BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE3766DD2AC5945500AAB575 /* NetworkProtectionUNNotificationPresenter.swift */; }; C10CB5F32A1A5BDF0048E503 /* AutofillViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = C10CB5F22A1A5BDF0048E503 /* AutofillViews.swift */; }; C111B26927F579EF006558B1 /* BookmarkOrFolderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C111B26827F579EF006558B1 /* BookmarkOrFolderTests.swift */; }; C12726EE2A5FF88C00215B02 /* EmailSignupPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C12726ED2A5FF88C00215B02 /* EmailSignupPromptView.swift */; }; @@ -4421,7 +4422,6 @@ BDA583852B98B69C00732FDC /* Subscription */ = { isa = PBXGroup; children = ( - BD15DB842B959CFD00821457 /* BundleExtension.swift */, BDA583862B98B6C700732FDC /* AccountManagerExtension.swift */, ); name = Subscription; @@ -4822,6 +4822,7 @@ children = ( EE7A92862AC6DE4700832A36 /* NetworkProtectionNotificationIdentifier.swift */, EE9D68DD2AE2A65600B55EF4 /* UserDefaults+NetworkProtection.swift */, + BD15DB842B959CFD00821457 /* BundleExtension.swift */, ); name = NetworkProtection; sourceTree = ""; @@ -6754,6 +6755,7 @@ D6E83C482B20C812006C8AFB /* SettingsHostingController.swift in Sources */, F46FEC5727987A5F0061D9DF /* KeychainItemsDebugViewController.swift in Sources */, D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */, + BDD3B3552B8EF8DB005857A8 /* NetworkProtectionUNNotificationPresenter.swift in Sources */, BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */, 02341FA62A4379CC008A1531 /* OnboardingStepViewModel.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, @@ -10017,7 +10019,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.0.2; + version = 116.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 141daf2b93..d017bc495e 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "5b354c6c91dc6059afd9b6f2b8f2b212d5b50046", - "version" : "116.0.2" + "revision" : "9bafa0271688bae67ab9c1ba97d5e69f80fe71df", + "version" : "116.1.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 72323da1e2..b0b88edc4a 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -74,6 +74,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() + private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults #endif private var autoClear: AutoClear? @@ -301,12 +302,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() #endif -#if NETWORK_PROTECTION && SUBSCRIPTION - if VPNSettings(defaults: .networkProtectionGroupDefaults).showEntitlementAlert { - presentExpiredEntitlementAlert() - } -#endif - RemoteMessaging.registerBackgroundRefreshTaskHandler( bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: AppDependencyProvider.shared.appSettings.favoritesDisplayMode @@ -354,13 +349,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate { window?.rootViewController?.present(alertController, animated: true, completion: nil) } +#if NETWORK_PROTECTION private func presentExpiredEntitlementAlert() { - let alertController = CriticalAlerts.makeExpiredEntitlementAlert() - window?.rootViewController?.present(alertController, animated: true) { - VPNSettings(defaults: .networkProtectionGroupDefaults).apply(change: .setShowEntitlementAlert(false)) + let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in + self?.mainViewController?.segueToPrivacyPro() + } + window?.rootViewController?.present(alertController, animated: true) { [weak self] in + self?.tunnelDefaults.showEntitlementAlert = false } } + private func presentExpiredEntitlementNotification() { + let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( + settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + defaults: .networkProtectionGroupDefaults, + wrappee: NetworkProtectionUNNotificationPresenter() + ) + presenter.showEntitlementNotification() + } +#endif + private func cleanUpMacPromoExperiment2() { UserDefaults.standard.removeObject(forKey: "com.duckduckgo.ios.macPromoMay23.exp2.cohort") } @@ -445,6 +453,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION widgetRefreshModel.refreshVPNWidget() + + if tunnelDefaults.showEntitlementAlert { + presentExpiredEntitlementAlert() + } + + presentExpiredEntitlementNotification() #endif } diff --git a/DuckDuckGo/CriticalAlerts.swift b/DuckDuckGo/CriticalAlerts.swift index c62183fe0c..0d3f25796f 100644 --- a/DuckDuckGo/CriticalAlerts.swift +++ b/DuckDuckGo/CriticalAlerts.swift @@ -70,19 +70,20 @@ struct CriticalAlerts { return alertController } - static func makeExpiredEntitlementAlert() -> UIAlertController { + static func makeExpiredEntitlementAlert(completion: @escaping () -> Void) -> UIAlertController { let alertController = UIAlertController(title: UserText.vpnAccessRevokedAlertTitle, message: UserText.vpnAccessRevokedAlertMessage, preferredStyle: .alert) alertController.overrideUserInterfaceStyle() let closeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionCancel, style: .cancel) - let signInButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in - UIApplication.shared.open(URL.emailProtectionQuickLink, options: [:], completionHandler: nil) + let subscribeButton = UIAlertAction(title: UserText.vpnAccessRevokedAlertActionSubscribe, style: .default) { _ in + completion() } alertController.addAction(closeButton) - alertController.addAction(signInButton) + alertController.addAction(subscribeButton) + alertController.preferredAction = subscribeButton return alertController } diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index c1e8a9560f..c352df8fb2 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -203,6 +203,14 @@ extension MainViewController { launchSettings() } + func segueToPrivacyPro() { + os_log(#function, log: .generalLog, type: .debug) + hideAllHighlightsIfNeeded() + launchSettings { + $0.shouldNavigateToSubscriptionFlow = true + } + } + func segueToDebugSettings() { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 41df7f48c2..160c7ee248 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -108,7 +108,8 @@ class MainViewController: UIViewController { private var emailCancellables = Set() #if NETWORK_PROTECTION - private var netpCancellables = Set() + private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults + private var vpnCancellables = Set() #endif private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger @@ -1333,9 +1334,56 @@ class MainViewController: UIViewController { .sink { [weak self] notification in self?.onNetworkProtectionAccountSignIn(notification) } - .store(in: &netpCancellables) + .store(in: &vpnCancellables) + + NotificationCenter.default.publisher(for: .vpnEntitlementMessagingDidChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.onNetworkProtectionEntitlementMessagingChange() + } + .store(in: &vpnCancellables) + + let notificationCallback: CFNotificationCallback = { _, _, name, _, _ in + if let name { + NotificationCenter.default.post(name: Notification.Name(name.rawValue as String), + object: nil) + } + } + + CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), + UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque()), + notificationCallback, + Notification.Name.vpnEntitlementMessagingDidChange.rawValue as CFString, + nil, .deliverImmediately) } - + + private func onNetworkProtectionEntitlementMessagingChange() { + if tunnelDefaults.showEntitlementAlert { + presentExpiredEntitlementAlert() + } + + presentExpiredEntitlementNotification() + } + + private func presentExpiredEntitlementAlert() { + let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in + self?.segueToPrivacyPro() + } + dismiss(animated: true) { + self.present(alertController, animated: true, completion: nil) + self.tunnelDefaults.showEntitlementAlert = false + } + } + + private func presentExpiredEntitlementNotification() { + let presenter = NetworkProtectionNotificationsPresenterTogglableDecorator( + settings: VPNSettings(defaults: .networkProtectionGroupDefaults), + defaults: .networkProtectionGroupDefaults, + wrappee: NetworkProtectionUNNotificationPresenter() + ) + presenter.showEntitlementNotification() + } + @objc private func onNetworkProtectionAccountSignIn(_ notification: Notification) { guard let token = AccountManager().accessToken else { @@ -1343,7 +1391,7 @@ class MainViewController: UIViewController { return } - VPNSettings(defaults: .networkProtectionGroupDefaults).resetEntitlementMessaging() + tunnelDefaults.resetEntitlementMessaging() print("[NetP Subscription] Reset expired entitlement messaging") Task { diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 73f2453a99..f3f98803ec 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -87,6 +87,8 @@ final class NetworkProtectionDebugViewController: UITableViewController { enum ExtensionDebugCommandRows: Int, CaseIterable { case triggerTestNotification case shutDown + case showEntitlementMessaging + case resetEntitlementMessaging } enum NetworkPathRows: Int, CaseIterable { @@ -364,6 +366,10 @@ final class NetworkProtectionDebugViewController: UITableViewController { cell.textLabel?.text = "Test Notification" case .shutDown: cell.textLabel?.text = "Disable VPN From Extension" + case .showEntitlementMessaging: + cell.textLabel?.text = "Show Entitlement Messaging" + case .resetEntitlementMessaging: + cell.textLabel?.text = "Reset Entitlement Messaging" case .none: break } @@ -379,6 +385,10 @@ final class NetworkProtectionDebugViewController: UITableViewController { Task { await NetworkProtectionDebugUtilities().disableConnectOnDemandAndShutDown() } + case .showEntitlementMessaging: + UserDefaults.networkProtectionGroupDefaults.enableEntitlementMessaging() + case .resetEntitlementMessaging: + UserDefaults.networkProtectionGroupDefaults.resetEntitlementMessaging() case .none: break } diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 8dc36fc915..4b2c71b8e5 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -209,7 +209,13 @@ struct SettingsSubscriptionView: View { } } }) - + + .onChange(of: viewModel.shouldNavigateToSubscriptionFlow, perform: { value in + if value { + isShowingsubScriptionFlow = true + } + }) + .onReceive(subscriptionFlowViewModel.$selectedFeature) { value in guard let value else { return } viewModel.onAppearNavigationTarget = value diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 30f48b19de..1e31387cd3 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -77,7 +77,8 @@ final class SettingsViewModel: ObservableObject { // Add more views as needed here... @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false - + @Published var shouldNavigateToSubscriptionFlow = false + @Published var shouldShowNetP = false @Published var shouldShowDBP = false @Published var shouldShowITP = false @@ -102,7 +103,7 @@ final class SettingsViewModel: ObservableObject { // Used to automatically navigate on Appear to a specific section enum SettingsSection: String { - case none, netP, dbp, itr + case none, netP, dbp, itr, subscriptionFlow } @Published var onAppearNavigationTarget: SettingsSection @@ -508,6 +509,8 @@ extension SettingsViewModel { self.shouldNavigateToDBP = true case .itr: self.shouldNavigateToITP = true + case .subscriptionFlow: + self.shouldNavigateToSubscriptionFlow = true default: break } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index bf53406e72..134db22317 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -950,6 +950,20 @@ But if you *do* want a peek under the hood, you can find more information about static let networkProtectionNotificationPromptTitle = NSLocalizedString("network-protection.waitlist.notification-prompt-title", value: "Know the instant you're invited", comment: "Title for the alert to confirm enabling notifications") static let networkProtectionNotificationPromptDescription = NSLocalizedString("network-protection.waitlist.notification-prompt-description", value: "Get a notification when your copy of Network Protection early access is ready.", comment: "Subtitle for the alert to confirm enabling notifications") + static let networkProtectionNotificationsTitle = NSLocalizedString("network.protection.notification.title", value: "DuckDuckGo", comment: "The title of the notifications shown from Network Protection") + static let networkProtectionConnectionSuccessNotificationBody = NSLocalizedString("network.protection.success.notification.body", value: "Network Protection is On. Your location and online activity are protected.", comment: "The body of the notification shown when Network Protection reconnects successfully") + static func networkProtectionConnectionSuccessNotificationBody(serverLocation: String) -> String { + let localized = NSLocalizedString( + "network.protection.success.notification.subtitle.including.serverLocation", + value: "Routing device traffic through %@.", + comment: "The body of the notification shown when Network Protection connects successfully with the city + state/country as formatted parameter" + ) + return String(format: localized, serverLocation) + } + static let networkProtectionConnectionInterruptedNotificationBody = NSLocalizedString("network.protection.interrupted.notification.body", value: "Network Protection was interrupted. Attempting to reconnect now...", comment: "The body of the notification shown when Network Protection's connection is interrupted") + static let networkProtectionConnectionFailureNotificationBody = NSLocalizedString("network.protection.failure.notification.body", value: "Network Protection failed to connect. Please try again later.", comment: "The body of the notification shown when Network Protection fails to reconnect") + static let networkProtectionEntitlementExpiredNotificationBody = NSLocalizedString("network.protection.entitlement.expired.notification.body", value: "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN.", comment: "The body of the notification when Privacy Pro subscription expired") + // MARK: Settings Screeen public static let settingsTitle = NSLocalizedString("settings.title", value: "Settings", comment: "Title for the Settings View") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 7025259c86..5654129084 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1528,6 +1528,15 @@ https://duckduckgo.com/mac"; /* Subtitle text for the Network Protection settings row */ "network-protection.waitlist.settings-subtitle.waitlist-not-joined" = "Join the private waitlist"; +/* The body of the notification when Privacy Pro subscription expired */ +"network.protection.entitlement.expired.notification.body" = "VPN disconnected due to expired subscription. Subscribe to Privacy Pro to reconnect DuckDuckGo VPN."; + +/* The body of the notification shown when Network Protection fails to reconnect */ +"network.protection.failure.notification.body" = "Network Protection failed to connect. Please try again later."; + +/* The body of the notification shown when Network Protection's connection is interrupted */ +"network.protection.interrupted.notification.body" = "Network Protection was interrupted. Attempting to reconnect now..."; + /* Message for the network protection invite dialog */ "network.protection.invite.dialog.message" = "Enter your invite code to get started."; @@ -1543,6 +1552,9 @@ https://duckduckgo.com/mac"; /* Title for the network protection invite success view */ "network.protection.invite.success.title" = "Success! You’re in."; +/* The title of the notifications shown from Network Protection */ +"network.protection.notification.title" = "DuckDuckGo"; + /* Title text for an iOS quick action that opens VPN settings */ "network.protection.quick-action.open-vpn" = "Open VPN"; @@ -1591,6 +1603,12 @@ https://duckduckgo.com/mac"; /* Title label text for the status view when netP is disconnected */ "network.protection.status.view.title" = "Network Protection"; +/* The body of the notification shown when Network Protection reconnects successfully */ +"network.protection.success.notification.body" = "Network Protection is On. Your location and online activity are protected."; + +/* The body of the notification shown when Network Protection connects successfully with the city + state/country as formatted parameter */ +"network.protection.success.notification.subtitle.including.serverLocation" = "Routing device traffic through %@."; + /* Title for the button to link to the iOS app settings and enable notifications app-wide. */ "network.protection.turn.on.notifications.button.title" = "Turn On Notifications"; diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index 408e9c6015..373694a2c8 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -217,6 +217,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) let notificationsPresenterDecorator = NetworkProtectionNotificationsPresenterTogglableDecorator( settings: settings, + defaults: .networkProtectionGroupDefaults, wrappee: notificationsPresenter ) notificationsPresenter.requestAuthorization() @@ -228,6 +229,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { debugEvents: Self.networkProtectionDebugEvents(controllerErrorStore: errorStore), providerEvents: Self.packetTunnelProviderEvents, settings: settings, + defaults: .networkProtectionGroupDefaults, isSubscriptionEnabled: isSubscriptionEnabled, entitlementCheck: Self.entitlementCheck) startMonitoringMemoryPressureEvents() diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift index 0afc858191..7e06bdbf1e 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionUNNotificationPresenter.swift @@ -105,7 +105,7 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio func showSupersededNotification() { } - func showEntitlementNotification(completion: @escaping (Error?) -> Void) { + func showEntitlementNotification() { let identifier = NetworkProtectionNotificationIdentifier.entitlement.rawValue let content = notificationContent(body: UserText.networkProtectionEntitlementExpiredNotificationBody) let request = UNNotificationRequest(identifier: identifier, content: content, trigger: .none) @@ -113,9 +113,7 @@ final class NetworkProtectionUNNotificationPresenter: NSObject, NetworkProtectio requestAlertAuthorization { authorized in guard authorized else { return } self.userNotificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier]) - self.userNotificationCenter.add(request) { error in - completion(error) - } + self.userNotificationCenter.add(request) } } From 5a4d936fdd2ce488c53cb8be1f68e5b64caed83c Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Thu, 7 Mar 2024 12:39:43 +0600 Subject: [PATCH 088/245] Add #URL macro (#2540) Task/Issue URL: https://app.asana.com/0/42792087274227/1206542455948401/f BSK PR: duckduckgo/BrowserServicesKit#657 toolbox PR: duckduckgo/apple-toolbox#2 --- .github/workflows/codeql.yml | 1 + .github/workflows/end-to-end.yml | 1 + .github/workflows/nightly.yml | 2 + .github/workflows/pr.yml | 2 + .github/workflows/sync-end-to-end.yml | 1 + Core/AppURLs.swift | 5 +- Core/BookmarksImporter.swift | 7 +- Core/DataStoreWarmup.swift | 3 +- Core/UserAgentManager.swift | 7 +- DuckDuckGo.xcodeproj/project.pbxproj | 70 ++++++++++++++++++- .../xcshareddata/swiftpm/Package.resolved | 17 +++-- .../AdAttribution/AdAttributionFetcher.swift | 3 +- .../AutofillLoginDetailsViewModel.swift | 9 +-- DuckDuckGo/DesktopDownloadViewModel.swift | 3 +- DuckDuckGo/FirewallManager.swift | 7 +- DuckDuckGo/RemoteMessageRequest.swift | 10 +-- .../AddressDisplayHelperTests.swift | 33 +++++---- DuckDuckGoTests/AppURLsTests.swift | 44 ++++++------ .../BookmarksCachingSearchTests.swift | 17 ++--- DuckDuckGoTests/BookmarksImporterTests.swift | 8 ++- DuckDuckGoTests/DaxDialogTests.swift | 24 ++++--- DuckDuckGoTests/DownloadMocks.swift | 3 +- DuckDuckGoTests/DownloadTestsHelper.swift | 4 +- .../FaviconRequestModifierTests.swift | 6 +- DuckDuckGoTests/FaviconsTests.swift | 12 ++-- .../FireproofFaviconUpdaterTests.swift | 12 ++-- DuckDuckGoTests/HTTPSUpgradeTests.swift | 15 ++-- .../MenuBookmarksViewModelTests.swift | 9 +-- DuckDuckGoTests/MockSecureVault.swift | 13 ++-- DuckDuckGoTests/MockUserAgent.swift | 5 +- .../NotFoundCachingDownloaderTests.swift | 8 ++- DuckDuckGoTests/PrivacyIconLogicTests.swift | 18 ++--- DuckDuckGoTests/TabTests.swift | 10 +-- DuckDuckGoTests/TabsModelTests.swift | 12 ++-- .../TrackerAnimationLogicTests.swift | 10 +-- DuckDuckGoTests/UserAgentTests.swift | 14 ++-- LocalPackages/DuckUI/Package.swift | 2 +- LocalPackages/SyncUI/Package.swift | 2 +- LocalPackages/Waitlist/Package.swift | 7 +- .../Network/ProductWaitlistRequest.swift | 5 +- .../Sources/WaitlistMocks/TestWaitlist.swift | 3 +- .../WaitlistViewModelTests.swift | 6 +- Widgets/WidgetViews.swift | 5 +- fastlane/Fastfile | 6 +- 44 files changed, 299 insertions(+), 162 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8a6d1ba63b..c1b68f040c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -44,6 +44,7 @@ jobs: -scheme "DuckDuckGo" \ -destination "platform=iOS Simulator,name=iPhone 14,OS=16.4" -skipPackagePluginValidation \ + -skipMacroValidation \ - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/end-to-end.yml b/.github/workflows/end-to-end.yml index 5cef39c0a7..68abd594b3 100644 --- a/.github/workflows/end-to-end.yml +++ b/.github/workflows/end-to-end.yml @@ -44,6 +44,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ ONLY_ACTIVE_ARCH=NO \ | tee xcodebuild.log diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index ae31bc6122..a53560b741 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -47,6 +47,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | tee xcodebuild.log \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml @@ -89,6 +90,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml - name: Publish unit tests report diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bdbdb037cc..462a3fc004 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -82,6 +82,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15,OS=17.2" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ DDG_SLOW_COMPILE_CHECK_THRESHOLD=250 \ | tee xcodebuild.log \ | xcbeautify --report junit --report-path . --junit-report-filename unittests.xml @@ -188,6 +189,7 @@ jobs: -derivedDataPath "DerivedData" \ -configuration "Release" \ -skipPackagePluginValidation \ + -skipMacroValidation \ | xcbeautify create-asana-task: diff --git a/.github/workflows/sync-end-to-end.yml b/.github/workflows/sync-end-to-end.yml index 78d7e2ad1e..af560b1dd5 100644 --- a/.github/workflows/sync-end-to-end.yml +++ b/.github/workflows/sync-end-to-end.yml @@ -44,6 +44,7 @@ jobs: -destination "platform=iOS Simulator,name=iPhone 15" \ -derivedDataPath "DerivedData" \ -skipPackagePluginValidation \ + -skipMacroValidation \ ONLY_ACTIVE_ARCH=NO \ | tee xcodebuild.log diff --git a/Core/AppURLs.swift b/Core/AppURLs.swift index e2250c3744..eff05031d3 100644 --- a/Core/AppURLs.swift +++ b/Core/AppURLs.swift @@ -17,8 +17,9 @@ // limitations under the License. // -import Foundation import BrowserServicesKit +import Foundation +import Macros public extension URL { @@ -48,7 +49,7 @@ public extension URL { static let exti = URL(string: "\(base)/exti/\(devMode)")! static let feedback = URL(string: "\(base)/feedback.js?type=app-feedback")! - static let appStore = URL(string: "https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361")! + static let appStore = #URL("https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361") static let mac = URL(string: "\(base)/mac")! static let windows = URL(string: "\(base)/windows")! diff --git a/Core/BookmarksImporter.swift b/Core/BookmarksImporter.swift index 177dcf2955..4615e47689 100644 --- a/Core/BookmarksImporter.swift +++ b/Core/BookmarksImporter.swift @@ -17,11 +17,12 @@ // limitations under the License. // +import Bookmarks import Common import Foundation -import SwiftSoup -import Bookmarks +import Macros import Persistence +import SwiftSoup public enum BookmarksImportError: Error { case invalidHtmlNoDLTag @@ -217,7 +218,7 @@ final public class BookmarksImporter { static let FavoritesFolder = "DuckDuckGo Favorites" static let BookmarksFolder = "DuckDuckGo Bookmarks" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = URL(string: "https://duckduckgo.com")! + static let bookmarkURL = #URL("https://duckduckgo.com") static let favoriteAttribute = "duckduckgo:favorite" static let isFavorite = "true" static let idAttribute = "id" diff --git a/Core/DataStoreWarmup.swift b/Core/DataStoreWarmup.swift index 79087b9fc2..6554f0ca8c 100644 --- a/Core/DataStoreWarmup.swift +++ b/Core/DataStoreWarmup.swift @@ -18,6 +18,7 @@ // import Combine +import Macros import WebKit /// WKWebsiteDataStore is basically non-functional until a web view has been instanciated and a page is successfully loaded. @@ -27,7 +28,7 @@ public class DataStoreWarmup { @MainActor public func ensureReady() async { - await BlockingNavigationDelegate().loadInBackgroundWebView(url: URL(string: "about:blank")!) + await BlockingNavigationDelegate().loadInBackgroundWebView(url: #URL("about:blank")) } } diff --git a/Core/UserAgentManager.swift b/Core/UserAgentManager.swift index 80db1e64a0..32f90094d2 100644 --- a/Core/UserAgentManager.swift +++ b/Core/UserAgentManager.swift @@ -19,10 +19,11 @@ // swiftlint:disable file_length -import Foundation -import WebKit import BrowserServicesKit import Common +import Foundation +import Macros +import WebKit public protocol UserAgentManager { @@ -46,7 +47,7 @@ public class DefaultUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) + webview.load(URLRequest.developerInitiated(#URL("about:blank"))) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ff266ce02e..2295ddf9f0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -687,6 +687,14 @@ B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9847BFFD27A2DDB400DB07AA /* ContentBlocking.swift */; }; B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DF11287C336E00C12A9C /* ContentBlockingUpdating.swift */; }; B652DF13287C373A00C12A9C /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DEFE287BF1FE00C12A9C /* ScriptSourceProviding.swift */; }; + B6A26C042B98358B00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C032B98358B00DF9EAD /* Macros */; }; + B6A26C062B98359A00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C052B98359A00DF9EAD /* Macros */; }; + B6A26C082B9835A000DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C072B9835A000DF9EAD /* Macros */; }; + B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C092B9835A800DF9EAD /* Macros */; }; + B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0B2B9835AD00DF9EAD /* Macros */; }; + B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0D2B9835B100DF9EAD /* Macros */; }; + B6A26C102B9835B400DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0F2B9835B400DF9EAD /* Macros */; }; + B6A26C122B9835B800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C112B9835B800DF9EAD /* Macros */; }; B6AD9E3628D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3428D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift */; }; B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3528D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift */; }; B6AD9E3828D4512E0019CDE9 /* EmbeddedTrackerDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9801F08927E4B21100191874 /* EmbeddedTrackerDataTests.swift */; }; @@ -2697,6 +2705,7 @@ 0202569029881ECA00E694E7 /* CocoaAsyncSocket in Frameworks */, 02025664298818B200E694E7 /* NetworkExtension.framework in Frameworks */, 4B470EE4299C6DFB0086EBDC /* Core.framework in Frameworks */, + B6A26C062B98359A00DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2704,6 +2713,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2722,6 +2732,7 @@ 853273B624FFE0BB00E3C778 /* WidgetKit.framework in Frameworks */, 0238E44F29C0FAA100615E30 /* FindInPageIOSJSSupport in Frameworks */, 3760DFED299315EF0045A446 /* Waitlist in Frameworks */, + B6A26C042B98358B00DF9EAD /* Macros in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */, 31E69A63280F4CB600478327 /* DuckUI in Frameworks */, @@ -2737,6 +2748,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */, F486D3362506A037002D07D7 /* OHHTTPStubs in Frameworks */, F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */, F115ED9C2B4EFC8E001A0453 /* TestUtils in Frameworks */, @@ -2767,6 +2779,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C102B9835B400DF9EAD /* Macros in Frameworks */, 1E1D8B632995143200C96994 /* OHHTTPStubs in Frameworks */, 1E1D8B652995143200C96994 /* OHHTTPStubsSwift in Frameworks */, ); @@ -2776,6 +2789,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */, F486D31D2506980E002D07D7 /* Swifter in Frameworks */, 85F21DC021123B03002631A6 /* Core.framework in Frameworks */, ); @@ -2786,6 +2800,7 @@ buildActionMask = 2147483647; files = ( 98D4B7DF2944DDBD0068814D /* Core.framework in Frameworks */, + B6A26C122B9835B800DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2794,6 +2809,7 @@ buildActionMask = 2147483647; files = ( 4B948E2629DCCDB9002531FA /* Persistence in Frameworks */, + B6A26C082B9835A000DF9EAD /* Macros in Frameworks */, 98A50962294B48A400D10880 /* Bookmarks in Frameworks */, 1E60989B290009C700A508F9 /* Common in Frameworks */, 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */, @@ -5692,6 +5708,7 @@ name = PacketTunnelProvider; packageProductDependencies = ( 0202568F29881ECA00E694E7 /* CocoaAsyncSocket */, + B6A26C052B98359A00DF9EAD /* Macros */, ); productName = PacketTunnelProvider; productReference = 02025662298818B100E694E7 /* PacketTunnelProvider.appex */; @@ -5712,6 +5729,9 @@ 025CCFE82582601C001CD5BB /* PBXTargetDependency */, ); name = FingerprintingUITests; + packageProductDependencies = ( + B6A26C0D2B9835B100DF9EAD /* Macros */, + ); productName = FingerprintingUITests; productReference = 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; @@ -5771,6 +5791,7 @@ F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, + B6A26C032B98358B00DF9EAD /* Macros */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -5796,6 +5817,7 @@ F486D3372506A225002D07D7 /* OHHTTPStubsSwift */, EEFAB4662A73C230008A38E4 /* NetworkProtectionTestUtils */, F115ED9B2B4EFC8E001A0453 /* TestUtils */, + B6A26C092B9835A800DF9EAD /* Macros */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -5859,6 +5881,7 @@ packageProductDependencies = ( 1E1D8B622995143200C96994 /* OHHTTPStubs */, 1E1D8B642995143200C96994 /* OHHTTPStubsSwift */, + B6A26C0F2B9835B400DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 85D33FCB25C97B6E002B91A6 /* IntegrationTests.xctest */; @@ -5881,6 +5904,7 @@ name = AtbUITests; packageProductDependencies = ( F486D31C2506980E002D07D7 /* Swifter */, + B6A26C0B2B9835AD00DF9EAD /* Macros */, ); productName = AtbIntegrationTests; productReference = 85F21DAD210F5E32002631A6 /* AtbUITests.xctest */; @@ -5902,6 +5926,7 @@ ); name = PerformanceTests; packageProductDependencies = ( + B6A26C112B9835B800DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */; @@ -5954,6 +5979,7 @@ EE8E56892A56BCE400F11DCA /* NetworkProtection */, D61CDA152B7CF77300A0FBB9 /* Subscription */, D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, + B6A26C072B9835A000DF9EAD /* Macros */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -10019,7 +10045,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 116.1.0; + version = 117.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10027,7 +10053,7 @@ repositoryURL = "https://github.com/duckduckgo/apple-toolbox.git"; requirement = { kind = exactVersion; - version = 1.0.0; + version = 2.0.0; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { @@ -10178,6 +10204,46 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; + B6A26C032B98358B00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C052B98359A00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C072B9835A000DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C092B9835A800DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0B2B9835AD00DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0D2B9835B100DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C0F2B9835B400DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; + B6A26C112B9835B800DF9EAD /* Macros */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = Macros; + }; B6F997CB2B8F380A00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d017bc495e..a17cb5bc7a 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/apple-toolbox.git", "state" : { - "revision" : "e3dc4faf70ca09718a2d20d5c47b449389e8c153", - "version" : "1.0.0" + "revision" : "d51beaf1736013b530576ace13a16d6d1a63742c", + "version" : "2.0.0" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "9bafa0271688bae67ab9c1ba97d5e69f80fe71df", - "version" : "116.1.0" + "revision" : "dbe75fa0ee9e3b740d520d5be7967e2c5239dfb5", + "version" : "117.0.0" } }, { @@ -135,6 +135,15 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", + "version" : "509.1.1" + } + }, { "identity" : "swifter", "kind" : "remoteSourceControl", diff --git a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift index a9d3d31a80..6193491b4f 100644 --- a/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift +++ b/DuckDuckGo/AdAttribution/AdAttributionFetcher.swift @@ -19,6 +19,7 @@ import AdServices import Common +import Macros protocol AdAttributionFetcher { func fetch() async -> AdServicesAttributionResponse? @@ -116,7 +117,7 @@ struct DefaultAdAttributionFetcher: AdAttributionFetcher { } private struct Constant { - static let attributionServiceURL = URL(string: "https://api-adservices.apple.com/api/v1/")! + static let attributionServiceURL = #URL("https://api-adservices.apple.com/api/v1/") static let maxRetries = 3 } } diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 2c7ec10da8..5aacbeea91 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -19,14 +19,15 @@ // swiftlint:disable file_length -import Foundation import BrowserServicesKit import Common -import SwiftUI import Core +import DDGSync import DesignResourcesKit +import Foundation +import Macros import SecureStorage -import DDGSync +import SwiftUI protocol AutofillLoginDetailsViewModelDelegate: AnyObject { func autofillLoginDetailsViewModelDidSave() @@ -58,7 +59,7 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } enum Constants { - static let privateEmailURL = URL(string: "https://duckduckgo.com/email")! + static let privateEmailURL = #URL("https://duckduckgo.com/email") } weak var delegate: AutofillLoginDetailsViewModelDelegate? diff --git a/DuckDuckGo/DesktopDownloadViewModel.swift b/DuckDuckGo/DesktopDownloadViewModel.swift index 6d52883345..9a14e3b360 100644 --- a/DuckDuckGo/DesktopDownloadViewModel.swift +++ b/DuckDuckGo/DesktopDownloadViewModel.swift @@ -18,11 +18,12 @@ // import Foundation +import Macros import UIKit final class DesktopDownloadViewModel: ObservableObject { - static let defaultURL = URL(string: "https://duckduckgo.com/")! + static let defaultURL = #URL("https://duckduckgo.com/") static let prefix = "https://" private var platform: DesktopDownloadPlatform diff --git a/DuckDuckGo/FirewallManager.swift b/DuckDuckGo/FirewallManager.swift index 6fb8e0a029..0035b6161c 100644 --- a/DuckDuckGo/FirewallManager.swift +++ b/DuckDuckGo/FirewallManager.swift @@ -23,6 +23,7 @@ import Foundation import NetworkExtension import BrowserServicesKit import Common +import Macros public protocol FirewallDelegate: AnyObject { func statusDidChange(newStatus: NEVPNStatus) @@ -80,10 +81,10 @@ public class FirewallManager: FirewallManaging { config.requestCachePolicy = .reloadIgnoringLocalCacheData config.urlCache = nil let session = URLSession(configuration: config) - let url = URL(string: "https://bad_url") - + let url = #URL("https://bad_url") + os_log("[INFO] Calling dummy URL to force VPN", log: FirewallManager.apptpLog, type: .debug) - _ = try? await session.data(from: url!) + _ = try? await session.data(from: url) os_log("[INFO] Response from dummy URL while activating VPN", log: FirewallManager.apptpLog, type: .debug) } diff --git a/DuckDuckGo/RemoteMessageRequest.swift b/DuckDuckGo/RemoteMessageRequest.swift index 2bfcd698fa..2aa0192467 100644 --- a/DuckDuckGo/RemoteMessageRequest.swift +++ b/DuckDuckGo/RemoteMessageRequest.swift @@ -17,20 +17,20 @@ // limitations under the License. // - -import Foundation import BrowserServicesKit -import RemoteMessaging import Core +import Foundation +import Macros import Networking +import RemoteMessaging public struct RemoteMessageRequest { private var endpoint: URL { #if DEBUG - return URL(string: "https://raw.githubusercontent.com/duckduckgo/remote-messaging-config/main/samples/ios/sample1.json")! + return #URL("https://raw.githubusercontent.com/duckduckgo/remote-messaging-config/main/samples/ios/sample1.json") #else - return URL(string: "https://staticcdn.duckduckgo.com/remotemessaging/config/v1/ios-config.json")! + return #URL("https://staticcdn.duckduckgo.com/remotemessaging/config/v1/ios-config.json") #endif } diff --git a/DuckDuckGoTests/AddressDisplayHelperTests.swift b/DuckDuckGoTests/AddressDisplayHelperTests.swift index c4122c4f20..e9fae0c49e 100644 --- a/DuckDuckGoTests/AddressDisplayHelperTests.swift +++ b/DuckDuckGoTests/AddressDisplayHelperTests.swift @@ -16,7 +16,10 @@ // See the License for the specific language governing permissions and // limitations under the License. // + +import Macros import XCTest + @testable import DuckDuckGo class AddressDisplayHelperTests: XCTestCase { @@ -25,36 +28,36 @@ class AddressDisplayHelperTests: XCTestCase { func testShortURL() { - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com")!), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com/some/path")!), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.subdomain.duckduckgo.com/some/path")!), "subdomain.duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://m.duckduckgo.com/some/path")!), "m.duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://some-other.sub.domain.duck.eu/with/path")!), "some-other.sub.domain.duck.eu") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://duckduckgo.com:1234")!), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://192.168.0.1:1234")!), "192.168.0.1") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.duckduckgo.com")), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.duckduckgo.com/some/path")), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.subdomain.duckduckgo.com/some/path")), "subdomain.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://m.duckduckgo.com/some/path")), "m.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("http://some-other.sub.domain.duck.eu/with/path")), "some-other.sub.domain.duck.eu") + XCTAssertEqual(AddressHelper.shortURLString(#URL("http://duckduckgo.com:1234")), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://192.168.0.1:1234")), "192.168.0.1") - XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.com")!), "com") // This is an exception we are ok with) + XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.com")), "com") // This is an exception we are ok with) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "file:///some/path")!)) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "somescheme:///some/path")!)) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "blob:https://www.my.com/111-222-333-444")!)) - XCTAssertNil(AddressHelper.shortURLString(URL(string: "data:text/plain;charset=UTF-8;page=21,the%20data:12345")!)) + XCTAssertNil(AddressHelper.shortURLString(#URL("file:///some/path"))) + XCTAssertNil(AddressHelper.shortURLString(#URL("somescheme:///some/path"))) + XCTAssertNil(AddressHelper.shortURLString(#URL("blob:https://www.my.com/111-222-333-444"))) + XCTAssertNil(AddressHelper.shortURLString(#URL("data:text/plain;charset=UTF-8;page=21,the%20data:12345"))) } func testShortensURLWhenShortVersionExpected() { - let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: false) + let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("http://some.domain.eu/with/path"), showsFullURL: false) XCTAssertEqual(addressForDisplay, "some.domain.eu") } func testDoesNotShortenURLWhenFullVersionExpected() { - let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: true) + let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("http://some.domain.eu/with/path"), showsFullURL: true) XCTAssertEqual(addressForDisplay, "http://some.domain.eu/with/path") } func testFallsBackToLongURLWhenCannotProduceShortURL() { - let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "file:///some/path")!, showsFullURL: false) + let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("file:///some/path"), showsFullURL: false) XCTAssertEqual(addressForDisplay, "file:///some/path") } diff --git a/DuckDuckGoTests/AppURLsTests.swift b/DuckDuckGoTests/AppURLsTests.swift index 280e855dcc..830fa92b30 100644 --- a/DuckDuckGoTests/AppURLsTests.swift +++ b/DuckDuckGoTests/AppURLsTests.swift @@ -17,7 +17,9 @@ // limitations under the License. // +import Macros import XCTest + @testable import BrowserServicesKit @testable import Core @@ -60,7 +62,7 @@ final class AppURLsTests: XCTestCase { } func testWhenRemoveInternalSearchParametersFromNonSearchUrlThenUrlIsUnchanged() { - let example = URL(string: "https://duckduckgo.com?atb=x&t=y&ko=z")! + let example = #URL("https://duckduckgo.com?atb=x&t=y&ko=z") let result = example.removingInternalSearchParameters() XCTAssertEqual(example.absoluteString, result.absoluteString) } @@ -76,13 +78,13 @@ final class AppURLsTests: XCTestCase { } func testBaseUrlDoesNotHaveSubDomain() { - XCTAssertEqual(URL.ddg, URL(string: "https://duckduckgo.com")) + XCTAssertEqual(URL.ddg, #URL("https://duckduckgo.com")) } func testWhenMobileStatsParamsAreAppliedThenTheyReturnAnUpdatedUrl() throws { mockStatisticsStore.atb = "x" let actual = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .applyingStatsParams(to: URL(string: "http://duckduckgo.com?atb=wrong&t=wrong")!) + .applyingStatsParams(to: #URL("http://duckduckgo.com?atb=wrong&t=wrong")) XCTAssertEqual(actual.getParameter(named: "atb"), "x") XCTAssertEqual(actual.getParameter(named: "t"), "ddg_ios") } @@ -90,70 +92,70 @@ final class AppURLsTests: XCTestCase { func testWhenAtbMatchesThenHasMobileStatsParamsIsTrue() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_ios")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_ios")) XCTAssertTrue(result) } func testWhenAtbIsMismatchedThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "y" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_ios")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_ios")) XCTAssertFalse(result) } func testWhenAtbIsMissingThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?t=ddg_ios")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?t=ddg_ios")) XCTAssertFalse(result) } func testWhenSourceIsMismatchedThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_desktop")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_desktop")) XCTAssertFalse(result) } func testWhenSourceIsMissingThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=y")!) + .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=y")) XCTAssertFalse(result) } func testWhenUrlIsDdgWithASearchParamThenIsSearchIsTrue() { - let result = URL(string: "http://duckduckgo.com?q=hello")!.isDuckDuckGoSearch + let result = #URL("http://duckduckgo.com?q=hello").isDuckDuckGoSearch XCTAssertTrue(result) } func testWhenUrlHasNoSearchParamsThenIsSearchIsFalse() { - let result = URL(string: "http://duckduckgo.com?test=hello")!.isDuckDuckGoSearch + let result = #URL("http://duckduckgo.com?test=hello").isDuckDuckGoSearch XCTAssertFalse(result) } func testWhenUrlIsNonDdgThenIsSearchIsFalse() { - let result = URL(string: "http://www.example.com?q=hello")!.isDuckDuckGoSearch + let result = #URL("http://www.example.com?q=hello").isDuckDuckGoSearch XCTAssertFalse(result) } func testWhenNonDdgUrlHasDdgParamThenIsDdgIsFalse() { - let result = URL(string: "http://www.example.com?x=duckduckgo.com")!.isDuckDuckGo + let result = #URL("http://www.example.com?x=duckduckgo.com").isDuckDuckGo XCTAssertFalse(result) } func testWhenDdgUrlIsHttpThenIsDddgIsTrue() { - let result = URL(string: "http://duckduckgo.com")!.isDuckDuckGo + let result = #URL("http://duckduckgo.com").isDuckDuckGo XCTAssertTrue(result) } func testWhenDdgUrlIsHttpsThenIsDddgIsTrue() { - let result = URL(string: "https://duckduckgo.com")!.isDuckDuckGo + let result = #URL("https://duckduckgo.com").isDuckDuckGo XCTAssertTrue(result) } func testWhenDdgUrlHasSubdomainThenIsDddgIsTrue() { - let result = URL(string: "http://www.duckduckgo.com")!.isDuckDuckGo + let result = #URL("http://www.duckduckgo.com").isDuckDuckGo XCTAssertTrue(result) } @@ -233,7 +235,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryUsesVerticalThenItIsAppliedToNewOne() throws { - let contextURL = URL(string: "https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images")! + let contextURL = #URL("https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images") let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -242,7 +244,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryUsesVerticalWithMapsThenTheseAreIgnored() throws { - let contextURL = URL(string: "https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images&iaxm=maps")! + let contextURL = #URL("https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images&iaxm=maps") let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -253,7 +255,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryHasNoVerticalThenItIsAbsentInNewOne() throws { - let contextURL = URL(string: "https://example.com")! + let contextURL = #URL("https://example.com") let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -273,20 +275,20 @@ final class AppURLsTests: XCTestCase { } func testWhenDdgUrlWithSearchParamThenSearchQueryReturned() { - let url = URL(string: "https://www.duckduckgo.com/?ko=-1&kl=wt-wt&q=some%20search")! + let url = #URL("https://www.duckduckgo.com/?ko=-1&kl=wt-wt&q=some%20search") let expected = "some search" let actual = url.searchQuery XCTAssertEqual(actual, expected) } func testWhenNoSearchParamInDdgUrlThenSearchQueryReturnsNil() { - let url = URL(string: "https://www.duckduckgo.com/?ko=-1&kl=wt-wt")! + let url = #URL("https://www.duckduckgo.com/?ko=-1&kl=wt-wt") let result = url.searchQuery XCTAssertNil(result) } func testWhenNotDdgUrlThenSearchQueryReturnsNil() { - let url = URL(string: "https://www.test.com/?ko=-1&kl=wt-wt&q=some%20search")! + let url = #URL("https://www.test.com/?ko=-1&kl=wt-wt&q=some%20search") let result = url.searchQuery XCTAssertNil(result) } diff --git a/DuckDuckGoTests/BookmarksCachingSearchTests.swift b/DuckDuckGoTests/BookmarksCachingSearchTests.swift index 7885ce8e8e..0efb242a11 100644 --- a/DuckDuckGoTests/BookmarksCachingSearchTests.swift +++ b/DuckDuckGoTests/BookmarksCachingSearchTests.swift @@ -17,10 +17,11 @@ // limitations under the License. // -import XCTest -import CoreData -import Combine import Bookmarks +import Combine +import CoreData +import Macros +import XCTest @testable import Core @@ -42,7 +43,7 @@ public class MockBookmarksSearchStore: BookmarksSearchStore { class BookmarksCachingSearchTests: XCTestCase { - let url = URL(string: "http://duckduckgo.com")! + let url = #URL("http://duckduckgo.com") let simpleStore = MockBookmarksSearchStore() let urlStore = MockBookmarksSearchStore() @@ -85,9 +86,9 @@ class BookmarksCachingSearchTests: XCTestCase { BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.f12a.rawValue, url: url, isFavorite: true)] urlStore.dataSet = [ - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample1.rawValue, url: URL(string: "https://example.com")!, isFavorite: true), - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample2.rawValue, url: URL(string: "https://example.com")!, isFavorite: true), - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlNasa.rawValue, url: URL(string: "https://www.nasa.gov")!, isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample1.rawValue, url: #URL("https://example.com"), isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample2.rawValue, url: #URL("https://example.com"), isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlNasa.rawValue, url: #URL("https://www.nasa.gov"), isFavorite: true), BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlDDG.rawValue, url: url, isFavorite: true)] } @@ -238,6 +239,6 @@ class BookmarksCachingSearchTests: XCTestCase { private extension BookmarksCachingSearchTests { enum Constants { static let bookmarkTitle = "my bookmark" - static let bookmarkURL = URL(string: "https://www.apple.com")! + static let bookmarkURL = #URL("https://www.apple.com") } } diff --git a/DuckDuckGoTests/BookmarksImporterTests.swift b/DuckDuckGoTests/BookmarksImporterTests.swift index e8a4dc44ee..94d2375de5 100644 --- a/DuckDuckGoTests/BookmarksImporterTests.swift +++ b/DuckDuckGoTests/BookmarksImporterTests.swift @@ -17,10 +17,12 @@ // limitations under the License. // -import XCTest +import Bookmarks +import Macros import SwiftSoup +import XCTest + @testable import Core -import Bookmarks @MainActor class BookmarksImporterTests: XCTestCase { @@ -195,6 +197,6 @@ private extension BookmarksImporterTests { enum Constants { static let bookmarkTitle = "my bookmark" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = URL(string: "https://duckduckgo.com")! + static let bookmarkURL = #URL("https://duckduckgo.com") } } diff --git a/DuckDuckGoTests/DaxDialogTests.swift b/DuckDuckGoTests/DaxDialogTests.swift index 727f21a99d..1d0072f0b5 100644 --- a/DuckDuckGoTests/DaxDialogTests.swift +++ b/DuckDuckGoTests/DaxDialogTests.swift @@ -17,13 +17,15 @@ // limitations under the License. // -import XCTest -@testable import DuckDuckGo -@testable import Core import BrowserServicesKit -import TrackerRadarKit import ContentBlocking +import Macros import PrivacyDashboard +import TrackerRadarKit +import XCTest + +@testable import Core +@testable import DuckDuckGo private struct MockEntityProvider: EntityProviding { @@ -46,13 +48,13 @@ final class DaxDialog: XCTestCase { struct URLs { - static let example = URL(string: "https://www.example.com")! - static let ddg = URL(string: "https://duckduckgo.com?q=test")! - static let facebook = URL(string: "https://www.facebook.com")! - static let google = URL(string: "https://www.google.com")! - static let ownedByFacebook = URL(string: "https://www.instagram.com")! - static let amazon = URL(string: "https://www.amazon.com")! - static let tracker = URL(string: "https://www.1dmp.io")! + static let example = #URL("https://www.example.com") + static let ddg = #URL("https://duckduckgo.com?q=test") + static let facebook = #URL("https://www.facebook.com") + static let google = #URL("https://www.google.com") + static let ownedByFacebook = #URL("https://www.instagram.com") + static let amazon = #URL("https://www.amazon.com") + static let tracker = #URL("https://www.1dmp.io") } diff --git a/DuckDuckGoTests/DownloadMocks.swift b/DuckDuckGoTests/DownloadMocks.swift index 1601392430..4ff1c9bd4a 100644 --- a/DuckDuckGoTests/DownloadMocks.swift +++ b/DuckDuckGoTests/DownloadMocks.swift @@ -18,6 +18,7 @@ // import Foundation +import Macros import WebKit @testable import DuckDuckGo @@ -55,7 +56,7 @@ class MockNavigationResponse: WKNavigationResponse { var mimeType: String? override var response: URLResponse { - let response = MockURLResponse(url: URL(string: "https://www.duck.com")!, + let response = MockURLResponse(url: #URL("https://www.duck.com"), mimeType: mimeType!, expectedContentLength: 1234, textEncodingName: "") diff --git a/DuckDuckGoTests/DownloadTestsHelper.swift b/DuckDuckGoTests/DownloadTestsHelper.swift index e9810b6c21..073e463402 100644 --- a/DuckDuckGoTests/DownloadTestsHelper.swift +++ b/DuckDuckGoTests/DownloadTestsHelper.swift @@ -18,10 +18,12 @@ // import Foundation +import Macros + @testable import DuckDuckGo struct DownloadTestsHelper { - let mockURL = URL(string: "https://duck.com")! + let mockURL = #URL("https://duck.com") let tmpDirectory = FileManager.default.temporaryDirectory let downloadsDirectory: URL diff --git a/DuckDuckGoTests/FaviconRequestModifierTests.swift b/DuckDuckGoTests/FaviconRequestModifierTests.swift index 0058e7a5c9..b3431cd777 100644 --- a/DuckDuckGoTests/FaviconRequestModifierTests.swift +++ b/DuckDuckGoTests/FaviconRequestModifierTests.swift @@ -17,8 +17,10 @@ // limitations under the License. // -import XCTest import BrowserServicesKit +import Macros +import XCTest + @testable import Core class MockEmbeddedDataProvider: EmbeddedDataProvider { @@ -74,7 +76,7 @@ class FaviconRequestModifierTests: XCTestCase { } func test() { - let request = URLRequest(url: URL(string: "https://www.example.com")!) + let request = URLRequest(url: #URL("https://www.example.com")) let result = FaviconRequestModifier(userAgentManager: userAgentManager).modified(for: request) XCTAssertTrue(result?.allHTTPHeaderFields?["User-Agent"]?.contains("DuckDuckGo") ?? false) } diff --git a/DuckDuckGoTests/FaviconsTests.swift b/DuckDuckGoTests/FaviconsTests.swift index efe00fe4d4..12d4bb494a 100644 --- a/DuckDuckGoTests/FaviconsTests.swift +++ b/DuckDuckGoTests/FaviconsTests.swift @@ -17,11 +17,13 @@ // limitations under the License. // +import Bookmarks +import CoreData +import Kingfisher +import Macros import XCTest + @testable import Core -import Kingfisher -import CoreData -import Bookmarks class FaviconsTests: XCTestCase { @@ -97,8 +99,8 @@ class FaviconsTests: XCTestCase { switch options?[4] { case .alternativeSources(let sources): XCTAssertEqual(2, sources.count) - XCTAssertEqual(sources[0].url, URL(string: "https://example.com/favicon.ico")) - XCTAssertEqual(sources[1].url, URL(string: "http://example.com/favicon.ico")) + XCTAssertEqual(sources[0].url, #URL("https://example.com/favicon.ico")) + XCTAssertEqual(sources[1].url, #URL("http://example.com/favicon.ico")) default: XCTFail("Unexpected option") diff --git a/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift b/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift index f8713a7307..2dfacdc021 100644 --- a/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift +++ b/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift @@ -17,12 +17,14 @@ // limitations under the License. // +import Bookmarks +import Core import Foundation +import Macros +import Persistence import XCTest + @testable import DuckDuckGo -import Persistence -import Core -import Bookmarks class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { @@ -85,7 +87,7 @@ class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { try createBookmark() image = UIImage() - let url = URL(string: "https://example.com/favicon.ico")! + let url = #URL("https://example.com/favicon.ico") let updater = FireproofFaviconUpdater(bookmarksDatabase: db, tab: self, favicons: self) updater.faviconUserScript(FaviconUserScript(), didRequestUpdateFaviconForHost: "example.com", withUrl: url) @@ -102,7 +104,7 @@ class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { try createBookmark() image = UIImage() - let url = URL(string: "https://example.com/favicon.ico")! + let url = #URL("https://example.com/favicon.ico") let updater = FireproofFaviconUpdater(bookmarksDatabase: db, tab: self, favicons: self) updater.faviconUserScript(FaviconUserScript(), didRequestUpdateFaviconForHost: "www.example.com", withUrl: url) diff --git a/DuckDuckGoTests/HTTPSUpgradeTests.swift b/DuckDuckGoTests/HTTPSUpgradeTests.swift index cc649cb09b..c32252598a 100644 --- a/DuckDuckGoTests/HTTPSUpgradeTests.swift +++ b/DuckDuckGoTests/HTTPSUpgradeTests.swift @@ -17,10 +17,11 @@ // limitations under the License. // -import XCTest +import BrowserServicesKit +import Macros import OHHTTPStubs import OHHTTPStubsSwift -import BrowserServicesKit +import XCTest @testable import Core @@ -34,7 +35,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpsThenShouldUpgradeResultIsFalse() { let expect = expectation(description: "Https url should not be upgraded") - let url = URL(string: "https://upgradable.url")! + let url = #URL("https://upgradable.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() @@ -49,7 +50,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsExcludedThenShouldUpgradeResultIsFalse() { let expect = expectation(description: "Excluded http:// urls should not be upgraded") - let url = URL(string: "http://excluded.url")! + let url = #URL("http://excluded.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() @@ -63,7 +64,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndCanBeUpgradedThenShouldUpgradeIsTrue() { let expect = expectation(description: "Http url in list and should be upgraded") - let url = URL(string: "http://upgradable.url")! + let url = #URL("http://upgradable.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter()), privacyConfig: WebKitTestHelper.preparePrivacyConfig( @@ -84,7 +85,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndHttpsUpgradesDisabledThenShouldUpgradeIsFalse() { let expect = expectation(description: "Http url in list and should not be upgraded") - let url = URL(string: "http://upgradable.url")! + let url = #URL("http://upgradable.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter()), privacyConfig: WebKitTestHelper.preparePrivacyConfig( @@ -105,7 +106,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndCannotBeUpgradedThenShouldUpgradeIsFalse() { let expect = expectation(description: "Http url not in list should not be upgraded") - let url = URL(string: "http://unknown.url")! + let url = #URL("http://unknown.url") let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() diff --git a/DuckDuckGoTests/MenuBookmarksViewModelTests.swift b/DuckDuckGoTests/MenuBookmarksViewModelTests.swift index d2bce8cb21..e4f310fbb4 100644 --- a/DuckDuckGoTests/MenuBookmarksViewModelTests.swift +++ b/DuckDuckGoTests/MenuBookmarksViewModelTests.swift @@ -17,11 +17,12 @@ // limitations under the License. // -import Foundation -import XCTest import Bookmarks -import Persistence import DuckDuckGo +import Foundation +import Macros +import Persistence +import XCTest private extension MenuBookmarksViewModel { @@ -35,7 +36,7 @@ private extension MenuBookmarksViewModel { class MenuBookmarksViewModelTests: XCTestCase { - let url = URL(string: "https://test.com")! + let url = #URL("https://test.com") var db: CoreDataDatabase! override func setUpWithError() throws { diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index 8545c641fa..ca0aea4c51 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -17,10 +17,11 @@ // limitations under the License. // -import Foundation import BrowserServicesKit -import SecureStorage +import Foundation import GRDB +import Macros +import SecureStorage // swiftlint:disable file_length typealias MockVaultFactory = SecureVaultFactory> @@ -222,6 +223,10 @@ final class MockSecureVault: AutofillSecureVault { // MARK: - Mock Providers +private extension URL { + static let duckduckgo = #URL("https://duckduckgo.com/") +} + class MockDatabaseProvider: AutofillDatabaseProvider { // swiftlint:disable identifier_name @@ -237,13 +242,13 @@ class MockDatabaseProvider: AutofillDatabaseProvider { var db: GRDB.DatabaseWriter // swiftlint:enable identifier_name - required init(file: URL = URL(string: "https://duckduckgo.com/")!, key: Data = Data()) throws { + required init(file: URL = .duckduckgo, key: Data = Data()) throws { db = (try? DatabaseQueue(named: "Test"))! } static func recreateDatabase(withKey key: Data) throws -> Self { // swiftlint:disable:next force_cast - return try MockDatabaseProvider(file: URL(string: "https://duck.com")!, key: Data()) as! Self + return try MockDatabaseProvider(file: #URL("https://duck.com"), key: Data()) as! Self } func storeWebsiteCredentials(_ credentials: SecureVaultModels.WebsiteCredentials) throws -> Int64 { diff --git a/DuckDuckGoTests/MockUserAgent.swift b/DuckDuckGoTests/MockUserAgent.swift index d9bb55ff33..49c9897345 100644 --- a/DuckDuckGoTests/MockUserAgent.swift +++ b/DuckDuckGoTests/MockUserAgent.swift @@ -17,9 +17,10 @@ // limitations under the License. // +import BrowserServicesKit import Foundation +import Macros import WebKit -import BrowserServicesKit class MockUserAgentManager: UserAgentManager { @@ -33,7 +34,7 @@ class MockUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) + webview.load(URLRequest.developerInitiated(#URL("about:blank"))) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned diff --git a/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift b/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift index 3bb8ed65c4..bb8f666020 100644 --- a/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift +++ b/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift @@ -17,7 +17,9 @@ // limitations under the License. // +import Macros import XCTest + @testable import Core class NotFoundCachingDownloaderTests: XCTestCase { @@ -57,7 +59,7 @@ class NotFoundCachingDownloaderTests: XCTestCase { downloader.noFaviconsFound(forDomain: "example.com") let moreThanAWeekFromNow = Date().addingTimeInterval(60 * 60 * 24 * 8) - XCTAssertTrue(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!, referenceDate: moreThanAWeekFromNow)) + XCTAssertTrue(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"), referenceDate: moreThanAWeekFromNow)) guard let domains: [String: TimeInterval] = UserDefaults.app.object(forKey: UserDefaultsWrapper.Key.notFoundCache.rawValue) as? [String: TimeInterval] else { @@ -70,11 +72,11 @@ class NotFoundCachingDownloaderTests: XCTestCase { func testWhenMarkingDomainAsNotFoundThenShouldNotDownload() { downloader.noFaviconsFound(forDomain: "example.com") - XCTAssertFalse(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!)) + XCTAssertFalse(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"))) } func testWhenDomainNotMarkedAsNotFoundThenShouldNotDownload() { - XCTAssertTrue(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!)) + XCTAssertTrue(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"))) } } diff --git a/DuckDuckGoTests/PrivacyIconLogicTests.swift b/DuckDuckGoTests/PrivacyIconLogicTests.swift index a14220bdc8..7ff6ad326f 100644 --- a/DuckDuckGoTests/PrivacyIconLogicTests.swift +++ b/DuckDuckGoTests/PrivacyIconLogicTests.swift @@ -17,21 +17,23 @@ // limitations under the License. // -import Foundation -import XCTest -import TrackerRadarKit import BrowserServicesKit +import Foundation +import Macros import PrivacyDashboard +import TrackerRadarKit +import XCTest + @testable import Core @testable import DuckDuckGo class PrivacyIconLogicTests: XCTestCase { - static let pageURL = URL(string: "https://example.com")! - static let insecurePageURL = URL(string: "http://example.com")! - static let ddgSearchURL = URL(string: "https://duckduckgo.com/?q=catfood&t=h_&ia=web")! - static let ddgMainURL = URL(string: "https://duckduckgo.com")! - static let ddgSupportURL = URL(string: "https://duckduckgo.com/email/settings/support")! + static let pageURL = #URL("https://example.com") + static let insecurePageURL = #URL("http://example.com") + static let ddgSearchURL = #URL("https://duckduckgo.com/?q=catfood&t=h_&ia=web") + static let ddgMainURL = #URL("https://duckduckgo.com") + static let ddgSupportURL = #URL("https://duckduckgo.com/email/settings/support") func testPrivacyIconIsShieldForPageURL() { let url = PrivacyIconLogicTests.insecurePageURL diff --git a/DuckDuckGoTests/TabTests.swift b/DuckDuckGoTests/TabTests.swift index c5092a92d8..e8eaa73ba4 100644 --- a/DuckDuckGoTests/TabTests.swift +++ b/DuckDuckGoTests/TabTests.swift @@ -17,16 +17,18 @@ // limitations under the License. // +import Macros import XCTest -@testable import DuckDuckGo + @testable import Core +@testable import DuckDuckGo class TabTests: XCTestCase { struct Constants { static let title = "A title" - static let url = URL(string: "https://example.com")! - static let differentUrl = URL(string: "https://aDifferentUrl.com")! + static let url = #URL("https://example.com") + static let differentUrl = #URL("https://aDifferentUrl.com") } func testWhenDesktopPropertyChangesThenObserversNotified() { @@ -145,7 +147,7 @@ class TabTests: XCTestCase { } private func link() -> Link { - return Link(title: "title", url: URL(string: "http://example.com")!) + return Link(title: "title", url: #URL("http://example.com")) } } diff --git a/DuckDuckGoTests/TabsModelTests.swift b/DuckDuckGoTests/TabsModelTests.swift index f81545afa2..405e4922ea 100644 --- a/DuckDuckGoTests/TabsModelTests.swift +++ b/DuckDuckGoTests/TabsModelTests.swift @@ -17,13 +17,15 @@ // limitations under the License. // +import Macros import XCTest + @testable import DuckDuckGo @testable import Core class TabsModelTests: XCTestCase { - private let exampleLink = Link(title: nil, url: URL(string: "https://example.com")!) + private let exampleLink = Link(title: nil, url: #URL("https://example.com")) private var emptyModel: TabsModel { return TabsModel(desktop: false) @@ -38,9 +40,9 @@ class TabsModelTests: XCTestCase { private var filledModel: TabsModel { let model = TabsModel(tabs: [ - Tab(link: Link(title: "url1", url: URL(string: "https://ur1l.com")!)), - Tab(link: Link(title: "url2", url: URL(string: "https://ur12.com")!)), - Tab(link: Link(title: "url3", url: URL(string: "https://ur13.com")!)) + Tab(link: Link(title: "url1", url: #URL("https://ur1l.com"))), + Tab(link: Link(title: "url2", url: #URL("https://ur12.com"))), + Tab(link: Link(title: "url3", url: #URL("https://ur13.com"))) ], desktop: false) return model } @@ -102,7 +104,7 @@ class TabsModelTests: XCTestCase { } func testWhenTabExistsThenIndexReturned() { - let tab = Tab(link: Link(title: nil, url: URL(string: "https://www.example.com")!)) + let tab = Tab(link: Link(title: nil, url: #URL("https://www.example.com"))) let testee = filledModel testee.add(tab: tab) XCTAssertEqual(testee.indexOf(tab: tab), 3) diff --git a/DuckDuckGoTests/TrackerAnimationLogicTests.swift b/DuckDuckGoTests/TrackerAnimationLogicTests.swift index 07302fc11f..b3088d01d7 100644 --- a/DuckDuckGoTests/TrackerAnimationLogicTests.swift +++ b/DuckDuckGoTests/TrackerAnimationLogicTests.swift @@ -17,18 +17,20 @@ // limitations under the License. // -import Foundation -import XCTest -import TrackerRadarKit import BrowserServicesKit import ContentBlocking +import Foundation +import Macros import PrivacyDashboard +import TrackerRadarKit +import XCTest + @testable import Core @testable import DuckDuckGo class TrackerAnimationLogicTests: XCTestCase { - static let pageURL = URL(string: "https://example.com")! + static let pageURL = #URL("https://example.com") func testAnimationLogicToAnimateTrackersIfAnyBlocked() { let trackerInfo = makeBlockedTrackerInfo(pageURL: Self.pageURL) diff --git a/DuckDuckGoTests/UserAgentTests.swift b/DuckDuckGoTests/UserAgentTests.swift index 7055db968e..22b6e6e509 100644 --- a/DuckDuckGoTests/UserAgentTests.swift +++ b/DuckDuckGoTests/UserAgentTests.swift @@ -17,9 +17,11 @@ // limitations under the License. // +import BrowserServicesKit +import Macros import WebKit import XCTest -import BrowserServicesKit + @testable import Core // swiftlint:disable file_length type_body_length @@ -64,11 +66,11 @@ final class UserAgentTests: XCTestCase { } private struct Constants { - static let url = URL(string: "http://example.com/index.html") - static let noAppUrl = URL(string: "http://cvs.com/index.html") - static let noAppSubdomainUrl = URL(string: "http://subdomain.cvs.com/index.html") - static let ddgFixedUrl = URL(string: "http://test2.com/index.html") - static let ddgDefaultUrl = URL(string: "http://test3.com/index.html") + static let url = #URL("http://example.com/index.html") + static let noAppUrl = #URL("http://cvs.com/index.html") + static let noAppSubdomainUrl = #URL("http://subdomain.cvs.com/index.html") + static let ddgFixedUrl = #URL("http://test2.com/index.html") + static let ddgDefaultUrl = #URL("http://test3.com/index.html") } let testConfig = """ diff --git a/LocalPackages/DuckUI/Package.swift b/LocalPackages/DuckUI/Package.swift index 79df959eeb..693478114c 100644 --- a/LocalPackages/DuckUI/Package.swift +++ b/LocalPackages/DuckUI/Package.swift @@ -31,7 +31,7 @@ let package = Package( targets: ["DuckUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "2.0.0"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 51c9b26b91..ebbe099b43 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -34,7 +34,7 @@ let package = Package( dependencies: [ .package(path: "../DuckUI"), .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0"), - .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "2.0.0"), ], targets: [ .target( diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index f3a3951ba5..b89a136609 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -16,12 +16,15 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/duckduckgo/DesignResourcesKit", exact: "2.0.0"), - .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "1.0.0"), + .package(url: "https://github.com/duckduckgo/apple-toolbox.git", exact: "2.0.0"), ], targets: [ .target( name: "Waitlist", - dependencies: ["DesignResourcesKit"], + dependencies: [ + "DesignResourcesKit", + .product(name: "Macros", package: "apple-toolbox"), + ], swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) ], diff --git a/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift b/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift index 63dab2041f..1058a46a3f 100644 --- a/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift +++ b/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift @@ -18,6 +18,7 @@ // import Foundation +import Macros public typealias ProductWaitlistMakeHTTPRequest = (URL, _ method: String, _ body: Data?, @escaping ProductWaitlistHTTPRequestCompletion) -> Void public typealias ProductWaitlistHTTPRequestCompletion = (Data?, Error?) -> Void @@ -133,9 +134,9 @@ public class ProductWaitlistRequest: WaitlistRequest { private var endpoint: URL { #if DEBUG - return URL(string: "https://quack.duckduckgo.com/api/auth/waitlist/")! + return #URL("https://quack.duckduckgo.com/api/auth/waitlist/") #else - return URL(string: "https://quack.duckduckgo.com/api/auth/waitlist/")! + return #URL("https://quack.duckduckgo.com/api/auth/waitlist/") #endif } diff --git a/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift b/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift index 51236cdf43..0dd29425fd 100644 --- a/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift +++ b/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift @@ -18,6 +18,7 @@ // import Foundation +import Macros import Waitlist public struct TestWaitlist: Waitlist { @@ -38,7 +39,7 @@ public struct TestWaitlist: Waitlist { public static var identifier: String = "mockIdentifier" public static var apiProductName: String = "mockApiProductName" - public static var downloadURL: URL = URL(string: "https://duckduckgo.com")! + public static var downloadURL: URL = #URL("https://duckduckgo.com") public static var backgroundTaskName: String = "BG Task" public static var backgroundRefreshTaskIdentifier: String = "bgtask" diff --git a/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift b/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift index fffb48ee09..3fc237560b 100644 --- a/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift +++ b/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift @@ -17,10 +17,12 @@ // limitations under the License. // -import XCTest import Combine +import Macros import UserNotifications import WaitlistMocks +import XCTest + @testable import Waitlist class WaitlistViewModelTests: XCTestCase { @@ -261,7 +263,7 @@ extension WaitlistViewModel { waitlistRequest: waitlistRequest, waitlistStorage: waitlistStorage, notificationService: notificationService, - downloadURL: URL(string: "https://duckduckgo.com")! + downloadURL: #URL("https://duckduckgo.com") ) } } diff --git a/Widgets/WidgetViews.swift b/Widgets/WidgetViews.swift index 60e6902090..81f71f056e 100644 --- a/Widgets/WidgetViews.swift +++ b/Widgets/WidgetViews.swift @@ -17,8 +17,9 @@ // limitations under the License. // -import WidgetKit +import Macros import SwiftUI +import WidgetKit struct FavoriteView: View { @@ -306,7 +307,7 @@ extension Image { struct WidgetViews_Previews: PreviewProvider { static let mockFavorites: [Favorite] = { - let duckDuckGoFavorite = Favorite(url: URL(string: "https://duckduckgo.com/")!, + let duckDuckGoFavorite = Favorite(url: #URL("https://duckduckgo.com/"), domain: "duckduckgo.com", title: "title", favicon: nil) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 751b5b0df9..0b02e8896e 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -78,7 +78,7 @@ lane :adhoc do |options| scheme: "DuckDuckGo", export_options: "adhocExportOptions.plist", derived_data_path: "DerivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) if is_ci @@ -184,7 +184,7 @@ private_lane :build_release do |options| scheme: "DuckDuckGo", export_options: "appStoreExportOptions.plist", derived_data_path: "DerivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) end @@ -197,7 +197,7 @@ private_lane :build_alpha do |options| scheme: "DuckDuckGo-Alpha", export_options: "alphaExportOptions.plist", derived_data_path: "DerivedData", - xcargs: "-skipPackagePluginValidation" + xcargs: "-skipPackagePluginValidation -skipMacroValidation" ) end From 5dcdabd90a759eeb5a6567a0bdc2cc917e149d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Thu, 7 Mar 2024 10:12:50 +0100 Subject: [PATCH 089/245] Cleanup after rolling out autoconsent enabled by default (#2537) --- Core/FeatureFlag.swift | 3 --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/AppUserDefaults.swift | 26 ++----------------- DuckDuckGo/Debug.storyboard | 25 ++++++------------ DuckDuckGo/RootDebugViewController.swift | 7 ----- DuckDuckGoTests/AppUserDefaultsTests.swift | 24 +---------------- 7 files changed, 14 insertions(+), 77 deletions(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 1246732321..0715f027d1 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -36,7 +36,6 @@ public enum FeatureFlag: String { case networkProtectionWaitlistActive case subscription case swipeTabs - case autoconsentOnByDefault case history } @@ -67,8 +66,6 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault)) case .incontextSignup: return .remoteReleasable(.feature(.incontextSignup)) - case .autoconsentOnByDefault: - return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault)) case .history: return .remoteReleasable(.feature(.history)) } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2295ddf9f0..34030af0a5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10045,7 +10045,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 117.0.0; + version = 118.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a17cb5bc7a..e3e64dc11f 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", "state" : { - "revision" : "dbe75fa0ee9e3b740d520d5be7967e2c5239dfb5", - "version" : "117.0.0" + "revision" : "2181cdcd27be961f10d988fbb202431a6ec6d56d", + "version" : "118.0.0" } }, { diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 446bd75fc3..7be4a03d4e 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -22,7 +22,6 @@ import Bookmarks import Core import WidgetKit -// swiftlint:disable file_length public class AppUserDefaults: AppSettings { public struct Notifications { @@ -291,29 +290,8 @@ public class AppUserDefaults: AppSettings { } } - var autoconsentEnabled: Bool { - get { - // Use settings value if present - if let isEnabled = autoconsentEnabledSetting { - return isEnabled - } - - // Use onByDefault rollout otherwise - return featureFlagger.isFeatureOn(.autoconsentOnByDefault) - } - - set { - autoconsentEnabledSetting = newValue - } - } - - // Only for testing and `DebugViewController` purposes - func clearAutoconsentUserSetting() { - autoconsentEnabledSetting = nil - } - - @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: false) - private var autoconsentEnabledSetting: Bool? + @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: true) + var autoconsentEnabled: Bool var inspectableWebViewEnabled: Bool { get { diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 7443344405..fdab02c1d8 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -233,17 +233,8 @@ - - - - - - - - - - + @@ -252,7 +243,7 @@ - + @@ -261,7 +252,7 @@ - + @@ -270,7 +261,7 @@ - + @@ -846,17 +837,17 @@ - + - + - + - + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/HistoryDebugViewController.swift b/DuckDuckGo/HistoryDebugViewController.swift new file mode 100644 index 0000000000..3d516818c3 --- /dev/null +++ b/DuckDuckGo/HistoryDebugViewController.swift @@ -0,0 +1,96 @@ +// +// HistoryDebugViewController.swift +// DuckDuckGo +// +// 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 UIKit +import SwiftUI +import History +import Core +import Combine +import Persistence + +class HistoryDebugViewController: UIHostingController { + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder, rootView: HistoryDebugRootView()) + } + +} + +struct HistoryDebugRootView: View { + + @ObservedObject var model = HistoryDebugViewModel() + + var body: some View { + List(model.history, id: \.id) { entry in + VStack(alignment: .leading) { + Text(entry.title ?? "") + .font(.system(size: 14)) + Text(entry.url?.absoluteString ?? "") + .font(.system(size: 12)) + Text(entry.lastVisit?.description ?? "") + .font(.system(size: 10)) + } + } + .navigationTitle("\(model.history.count) History Items") + .toolbar { + if #available(iOS 15, *) { + Button("Delete All", role: .destructive) { + model.deleteAll() + } + } + } + } + +} + +class HistoryDebugViewModel: ObservableObject { + + @Published var history: [BrowsingHistoryEntryManagedObject] = [] + + let database: CoreDataDatabase + + init() { + database = HistoryDatabase.make() + database.loadStore() + fetch() + } + + func deleteAll() { + let context = database.makeContext(concurrencyType: .mainQueueConcurrencyType) + let fetchRequest = BrowsingHistoryEntryManagedObject.fetchRequest() + let items = try? context.fetch(fetchRequest) + items?.forEach { obj in + context.delete(obj) + } + do { + try context.save() + } catch { + assertionFailure("Failed to save after delete all") + } + fetch() + } + + func fetch() { + let context = database.makeContext(concurrencyType: .mainQueueConcurrencyType) + let fetchRequest = BrowsingHistoryEntryManagedObject.fetchRequest() + fetchRequest.returnsObjectsAsFaults = false + history = (try? context.fetch(fetchRequest)) ?? [] + } + +} diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index b3e04842cf..704e2098ea 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -161,6 +161,7 @@ class MainViewController: UIViewController { fatalError("Use init?(code:") } + var historyManager: HistoryManager var viewCoordinator: MainViewCoordinator! #if APP_TRACKING_PROTECTION @@ -168,6 +169,7 @@ class MainViewController: UIViewController { bookmarksDatabase: CoreDataDatabase, bookmarksDatabaseCleaner: BookmarkDatabaseCleaner, appTrackingProtectionDatabase: CoreDataDatabase, + historyManager: HistoryManager, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, appSettings: AppSettings = AppUserDefaults() @@ -175,6 +177,7 @@ class MainViewController: UIViewController { self.appTrackingProtectionDatabase = appTrackingProtectionDatabase self.bookmarksDatabase = bookmarksDatabase self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner + self.historyManager = historyManager self.syncService = syncService self.syncDataProviders = syncDataProviders self.favoritesViewModel = FavoritesListViewModel(bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: appSettings.favoritesDisplayMode) @@ -190,12 +193,14 @@ class MainViewController: UIViewController { init( bookmarksDatabase: CoreDataDatabase, bookmarksDatabaseCleaner: BookmarkDatabaseCleaner, + historyManager: HistoryManager, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, appSettings: AppSettings ) { self.bookmarksDatabase = bookmarksDatabase self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner + self.historyManager = historyManager self.syncService = syncService self.syncDataProviders = syncDataProviders self.favoritesViewModel = FavoritesListViewModel(bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: appSettings.favoritesDisplayMode) @@ -706,6 +711,7 @@ class MainViewController: UIViewController { tabManager = TabManager(model: tabsModel, previewsSource: previewsSource, bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, syncService: syncService, delegate: self) } @@ -2258,6 +2264,8 @@ extension MainViewController: AutoClearWorker { self.bookmarksDatabaseCleaner?.cleanUpDatabaseNow() } + await historyManager.removeAllHistory() + self.clearInProgress = false self.postClear?() diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 68a8fa473a..a6bf166cf2 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -23,6 +23,7 @@ import DDGSync import WebKit import BrowserServicesKit import Persistence +import History class TabManager { @@ -31,6 +32,7 @@ class TabManager { private var tabControllerCache = [TabViewController]() private let bookmarksDatabase: CoreDataDatabase + private let historyManager: HistoryManager private let syncService: DDGSyncing private var previewsSource: TabPreviewsSource weak var delegate: TabDelegate? @@ -42,11 +44,13 @@ class TabManager { init(model: TabsModel, previewsSource: TabPreviewsSource, bookmarksDatabase: CoreDataDatabase, + historyManager: HistoryManager, syncService: DDGSyncing, delegate: TabDelegate) { self.model = model self.previewsSource = previewsSource self.bookmarksDatabase = bookmarksDatabase + self.historyManager = historyManager self.syncService = syncService self.delegate = delegate let index = model.currentIndex @@ -68,7 +72,10 @@ class TabManager { @MainActor private func buildController(forTab tab: Tab, url: URL?, inheritedAttribution: AdClickAttributionLogic.State?) -> TabViewController { let configuration = WKWebViewConfiguration.persistent() - let controller = TabViewController.loadFromStoryboard(model: tab, bookmarksDatabase: bookmarksDatabase, syncService: syncService) + let controller = TabViewController.loadFromStoryboard(model: tab, + bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, + syncService: syncService) controller.applyInheritedAttribution(inheritedAttribution) controller.attachWebView(configuration: configuration, andLoadRequest: url == nil ? nil : URLRequest.userInitiated(url!), @@ -137,7 +144,10 @@ class TabManager { model.insert(tab: tab, at: model.currentIndex + 1) model.select(tabAt: model.currentIndex + 1) - let controller = TabViewController.loadFromStoryboard(model: tab, bookmarksDatabase: bookmarksDatabase, syncService: syncService) + let controller = TabViewController.loadFromStoryboard(model: tab, + bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, + syncService: syncService) controller.attachWebView(configuration: configCopy, andLoadRequest: request, consumeCookies: !model.hasActiveTabs, diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 5e0e482895..7ecc8667ea 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -18,8 +18,8 @@ // import WebKit -import Combine import Core +import Combine import StoreKit import LocalAuthentication import BrowserServicesKit @@ -34,6 +34,7 @@ import ContentBlocking import TrackerRadarKit import Networking import SecureStorage +import History #if NETWORK_PROTECTION import NetworkProtection @@ -175,6 +176,7 @@ class TabViewController: UIViewController { didSet { updateTabModel() delegate?.tabLoadingStateDidChange(tab: self) + historyCapture.titleDidChange(title, forURL: url) } } @@ -272,6 +274,7 @@ class TabViewController: UIViewController { static func loadFromStoryboard(model: Tab, appSettings: AppSettings = AppDependencyProvider.shared.appSettings, bookmarksDatabase: CoreDataDatabase, + historyManager: HistoryManager, syncService: DDGSyncing) -> TabViewController { let storyboard = UIStoryboard(name: "Tab", bundle: nil) let controller = storyboard.instantiateViewController(identifier: "TabViewController", creator: { coder in @@ -279,6 +282,7 @@ class TabViewController: UIViewController { tabModel: model, appSettings: appSettings, bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, syncService: syncService) }) return controller @@ -287,15 +291,21 @@ class TabViewController: UIViewController { private var userContentController: UserContentController { (webView.configuration.userContentController as? UserContentController)! } - + + let historyManager: HistoryManager + let historyCapture: HistoryCapture + required init?(coder aDecoder: NSCoder, tabModel: Tab, appSettings: AppSettings, bookmarksDatabase: CoreDataDatabase, + historyManager: HistoryManager, syncService: DDGSyncing) { self.tabModel = tabModel self.appSettings = appSettings self.bookmarksDatabase = bookmarksDatabase + self.historyManager = historyManager + self.historyCapture = HistoryCapture(historyManager: historyManager) self.syncService = syncService super.init(coder: aDecoder) } @@ -303,7 +313,7 @@ class TabViewController: UIViewController { required init?(coder aDecoder: NSCoder) { fatalError("Not implemented") } - + override func viewDidLoad() { super.viewDidLoad() @@ -380,7 +390,8 @@ class TabViewController: UIViewController { func updateTabModel() { if let url = url { - tabModel.link = Link(title: title, url: url) + let link = Link(title: title, url: url) + tabModel.link = link } else { tabModel.link = nil } @@ -1021,7 +1032,9 @@ extension TabViewController: WKNavigationDelegate { } func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) { + if let url = webView.url { + historyCapture.webViewDidCommit(url: url) instrumentation.willLoad(url: url) } @@ -1058,6 +1071,7 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { + let mimeType = MIMEType(from: navigationResponse.response.mimeType) let httpResponse = navigationResponse.response as? HTTPURLResponse @@ -1130,6 +1144,7 @@ extension TabViewController: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { + adClickAttributionDetection.onDidFinishNavigation(url: webView.url) adClickAttributionLogic.onDidFinishNavigation(host: webView.url?.host) hideProgressIndicator() diff --git a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift index a4ef959ea7..d760a85b0a 100644 --- a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift @@ -21,6 +21,9 @@ import UIKit import Core import SafariServices import WebKit +import History +import Common +import Combine extension TabViewController { @@ -99,7 +102,10 @@ extension TabViewController { fileprivate func buildOpenLinkPreview(for url: URL) -> UIViewController? { let tab = Tab(link: Link(title: nil, url: url)) - let tabController = TabViewController.loadFromStoryboard(model: tab, bookmarksDatabase: bookmarksDatabase, syncService: syncService) + let tabController = TabViewController.loadFromStoryboard(model: tab, + bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, + syncService: syncService) tabController.isLinkPreview = true tabController.decorate(with: ThemeManager.shared.currentTheme) let configuration = WKWebViewConfiguration.nonPersistent() diff --git a/DuckDuckGoTests/HistoryCaptureTests.swift b/DuckDuckGoTests/HistoryCaptureTests.swift new file mode 100644 index 0000000000..78b65f955a --- /dev/null +++ b/DuckDuckGoTests/HistoryCaptureTests.swift @@ -0,0 +1,105 @@ +// +// HistoryCaptureTests.swift +// DuckDuckGo +// +// 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 XCTest +import BrowserServicesKit +import Persistence +import History +@testable import Core + +final class HistoryCaptureTests: XCTestCase { + + let mockHistoryCoordinator = MockHistoryCoordinator() + + func test_whenURLIsCommitted_ThenVisitIsStored() { + let capture = makeCapture() + capture.webViewDidCommit(url: URL.example) + XCTAssertEqual(1, mockHistoryCoordinator.addVisitCalls.count) + XCTAssertEqual([URL.example], mockHistoryCoordinator.addVisitCalls) + } + + func test_whenURLIsDDGQuery_ThenOnlyQueryIsStored() { + let capture = makeCapture() + capture.webViewDidCommit(url: URL.makeSearchURL(query: "test")!) + XCTAssertEqual(1, mockHistoryCoordinator.addVisitCalls.count) + XCTAssertEqual("https://duckduckgo.com?q=test", mockHistoryCoordinator.addVisitCalls[0].absoluteString) + } + + func test_whenURLIsDDGQueryWithExtraParams_ThenOnlyQueryIsStored() { + let capture = makeCapture() + capture.webViewDidCommit(url: URL.makeSearchURL(query: "test")!.appendingParameter(name: "ia", value: "web")) + XCTAssertEqual(1, mockHistoryCoordinator.addVisitCalls.count) + XCTAssertEqual("https://duckduckgo.com?q=test", mockHistoryCoordinator.addVisitCalls[0].absoluteString) + } + + func test_whenTitleIsUpdatedForMatchingURL_ThenTitleIsSaved() { + let capture = makeCapture() + capture.webViewDidCommit(url: URL.example) + capture.titleDidChange("test", forURL: URL.example) + XCTAssertEqual(1, mockHistoryCoordinator.updateTitleIfNeededCalls.count) + XCTAssertEqual(mockHistoryCoordinator.updateTitleIfNeededCalls[0].title, "test") + XCTAssertEqual(mockHistoryCoordinator.updateTitleIfNeededCalls[0].url, URL.example) + } + + func test_whenTitleIsUpdatedForDifferentURL_ThenTitleIsIgnored() { + let capture = makeCapture() + capture.webViewDidCommit(url: URL.example) + capture.titleDidChange("test", forURL: URL.example.appendingPathComponent("path")) + XCTAssertEqual(0, mockHistoryCoordinator.updateTitleIfNeededCalls.count) + } + + func makeCapture() -> HistoryCapture { + return HistoryCapture(historyManager: MockHistoryManager(historyCoordinator: mockHistoryCoordinator)) + } + +} + +class MockHistoryCoordinator: NullHistoryCoordinator { + + var addVisitCalls = [URL]() + var updateTitleIfNeededCalls = [(title: String, url: URL)]() + + override func addVisit(of url: URL) -> Visit? { + addVisitCalls.append(url) + return nil + } + + override func updateTitleIfNeeded(title: String, url: URL) { + updateTitleIfNeededCalls.append((title: title, url: url)) + } + +} + +private extension URL { + static let example = URL(string: "https://example.com")! +} + +class MockHistoryManager: HistoryManaging { + + let historyCoordinator: HistoryCoordinating + + init(historyCoordinator: HistoryCoordinating) { + self.historyCoordinator = historyCoordinator + } + + func loadStore() { + } + +} diff --git a/DuckDuckGoTests/HistoryManagerTests.swift b/DuckDuckGoTests/HistoryManagerTests.swift index c969773b29..50828b8eda 100644 --- a/DuckDuckGoTests/HistoryManagerTests.swift +++ b/DuckDuckGoTests/HistoryManagerTests.swift @@ -20,17 +20,15 @@ import Foundation import XCTest import BrowserServicesKit +import Persistence +import History @testable import Core final class HistoryManagerTests: XCTestCase { - let privacyConfig = MockPrivacyConfiguration() + let privacyConfigManager = MockPrivacyConfigurationManager() var variantManager = MockVariantManager() - lazy var historyManager: HistoryManager = { - HistoryManager(privacyConfig: privacyConfig, variantManager: variantManager) - }() - func test() { struct Condition { @@ -48,6 +46,7 @@ final class HistoryManagerTests: XCTestCase { ] let privacyConfig = MockPrivacyConfiguration() + let privacyConfigManager = MockPrivacyConfigurationManager() var variantManager = MockVariantManager() for condition in conditions { @@ -56,11 +55,41 @@ final class HistoryManagerTests: XCTestCase { return condition.privacy } + privacyConfigManager.privacyConfig = privacyConfig variantManager.isSupportedReturns = condition.variant - let historyManager = HistoryManager(privacyConfig: privacyConfig, variantManager: variantManager) + let model = CoreDataDatabase.loadModel(from: History.bundle, named: "BrowsingHistory")! + let db = CoreDataDatabase(name: "Test", containerLocation: tempDBDir(), model: model) + + let historyManager = HistoryManager(privacyConfigManager: privacyConfigManager, variantManager: variantManager, database: db) { + XCTFail("DB Error \($0)") + } XCTAssertEqual(condition.expected, historyManager.isHistoryFeatureEnabled(), String(describing: condition)) } } + + func test_WhenManagerFailsToLoadStore_ThenThrowsError() { + let privacyConfig = MockPrivacyConfiguration() + let privacyConfigManager = MockPrivacyConfigurationManager() + var variantManager = MockVariantManager() + + privacyConfig.isFeatureKeyEnabled = { feature, _ in + XCTAssertEqual(feature, .history) + return true + } + + privacyConfigManager.privacyConfig = privacyConfig + variantManager.isSupportedReturns = true + + let model = CoreDataDatabase.loadModel(from: History.bundle, named: "BrowsingHistory")! + let db = CoreDataDatabase(name: "Test", containerLocation: URL.aboutLink, model: model) + + var error: Error? + let historyManager = HistoryManager(privacyConfigManager: privacyConfigManager, variantManager: variantManager, database: db) { + error = $0 + } + _ = historyManager.historyCoordinator + XCTAssertNotNil(error) + } } From bbacdd73a2ef35e4010ead1f49081a07d29e4796 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Mar 2024 23:42:32 +0100 Subject: [PATCH 112/245] Subscriptions: 19. Error handling and minor updates (#2567) --- DuckDuckGo/SettingsSubscriptionView.swift | 14 ++-- DuckDuckGo/SettingsViewModel.swift | 18 ++--- .../AsyncHeadlessWebView.swift | 3 + .../AsyncHeadlessWebViewModel.swift | 1 + .../HeadlessWebView.swift | 2 + .../HeadlessWebViewCoordinator.swift | 11 ++- ...IdentityTheftRestorationPagesFeature.swift | 9 ++- .../SubscriptionEmailViewModel.swift | 12 ++++ .../ViewModel/SubscriptionFlowViewModel.swift | 23 +++++- .../ViewModel/SubscriptionITPViewModel.swift | 15 +++- .../SubscriptionRestoreViewModel.swift | 2 +- .../Views/SubscriptionEmailView.swift | 9 +++ .../Views/SubscriptionFlowView.swift | 70 +++++++++++++++---- .../Views/SubscriptionITPView.swift | 11 +++ .../Views/SubscriptionRestoreView.swift | 12 +++- DuckDuckGo/UserText.swift | 4 ++ DuckDuckGo/en.lproj/Localizable.strings | 9 +++ submodules/privacy-reference-tests | 2 +- 18 files changed, 187 insertions(+), 40 deletions(-) diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 4b2c71b8e5..a727b87dc0 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -138,18 +138,14 @@ struct SettingsSubscriptionView: View { SettingsCellView(label: UserText.settingsPProDBPTitle, subtitle: UserText.settingsPProDBPSubTitle, action: { isShowingDBP.toggle() }, isButton: true) - .sheet(isPresented: $isShowingDBP) { - SubscriptionPIRView() - } + } if viewModel.shouldShowITP { SettingsCellView(label: UserText.settingsPProITRTitle, subtitle: UserText.settingsPProITRSubTitle, action: { isShowingITP.toggle() }, isButton: true) - .sheet(isPresented: $isShowingITP) { - SubscriptionITPView() - } + } NavigationLink(destination: SubscriptionSettingsView()) { @@ -157,6 +153,12 @@ struct SettingsSubscriptionView: View { } } + .sheet(isPresented: $isShowingDBP) { + SubscriptionPIRView() + } + .sheet(isPresented: $isShowingITP) { + SubscriptionITPView() + } } var body: some View { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index fd7ea5e1de..7aaba476b6 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -25,6 +25,7 @@ import Common import Combine import SyncUI + #if SUBSCRIPTION import Subscription #endif @@ -47,12 +48,20 @@ final class SettingsViewModel: ObservableObject { private var legacyViewProvider: SettingsLegacyViewProvider private lazy var versionProvider: AppVersion = AppVersion.shared private let voiceSearchHelper: VoiceSearchHelperProtocol + #if SUBSCRIPTION private var accountManager: AccountManager private var signOutObserver: Any? + + // Sheet Presentation & Navigation @Published var isRestoringSubscription: Bool = false @Published var shouldDisplayRestoreSubscriptionError: Bool = false + @Published var shouldShowNetP = false + @Published var shouldShowDBP = false + @Published var shouldShowITP = false #endif + @UserDefaultsWrapper(key: .subscriptionIsActive, defaultValue: false) + static private var cachedHasActiveSubscription: Bool #if NETWORK_PROTECTION @@ -63,10 +72,6 @@ final class SettingsViewModel: ObservableObject { private lazy var isPad = UIDevice.current.userInterfaceIdiom == .pad private var cancellables = Set() - // Defaults - @UserDefaultsWrapper(key: .subscriptionIsActive, defaultValue: false) - static private var cachedHasActiveSubscription: Bool - // Closures to interact with legacy view controllers through the container var onRequestPushLegacyView: ((UIViewController) -> Void)? var onRequestPresentLegacyView: ((UIViewController, _ modal: Bool) -> Void)? @@ -78,10 +83,6 @@ final class SettingsViewModel: ObservableObject { @Published var shouldNavigateToDBP = false @Published var shouldNavigateToITP = false @Published var shouldNavigateToSubscriptionFlow = false - - @Published var shouldShowNetP = false - @Published var shouldShowDBP = false - @Published var shouldShowITP = false // Our View State @Published private(set) var state: SettingsState @@ -372,7 +373,6 @@ extension SettingsViewModel { } } } - default: // Account is active but there's not a valid subscription / entitlements signOutUser() diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift index aa6a8fdbce..306a636443 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebView.swift @@ -65,6 +65,9 @@ struct AsyncHeadlessWebView: View { onContentType: { value in viewModel.contentType = value }, + onNavigationError: { value in + viewModel.navigationError = value + }, navigationCoordinator: viewModel.navigationCoordinator ) .frame(width: geometry.size.width, height: geometry.size.height) diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift index 190352d246..688f0ed213 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift @@ -37,6 +37,7 @@ final class AsyncHeadlessWebViewViewModel: ObservableObject { @Published var canGoBack: Bool = false @Published var canGoForward: Bool = false @Published var contentType: String = "" + @Published var navigationError: Error? @Published var allowedDomains: [String]? var navigationCoordinator = HeadlessWebViewNavCoordinator(webView: nil) diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift index 7467489ac2..3452302d90 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebView.swift @@ -32,6 +32,7 @@ struct HeadlessWebView: UIViewRepresentable { var onCanGoBack: ((Bool) -> Void)? var onCanGoForward: ((Bool) -> Void)? var onContentType: ((String) -> Void)? + var onNavigationError: ((Error?) -> Void)? var navigationCoordinator: HeadlessWebViewNavCoordinator func makeUIView(context: Context) -> WKWebView { @@ -73,6 +74,7 @@ struct HeadlessWebView: UIViewRepresentable { onCanGoBack: onCanGoBack, onCanGoForward: onCanGoForward, onContentType: onContentType, + onNavigationError: onNavigationError, settings: settings ) } diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift index 92c8f1a7a4..d8fc66a461 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift @@ -30,6 +30,7 @@ final class HeadlessWebViewCoordinator: NSObject { var onCanGoBack: ((Bool) -> Void)? var onCanGoForward: ((Bool) -> Void)? var onContentType: ((String) -> Void)? + var onNavigationError: ((Error?) -> Void)? var settings: AsyncHeadlessWebViewSettings var size: CGSize = .zero @@ -52,6 +53,7 @@ final class HeadlessWebViewCoordinator: NSObject { onCanGoBack: ((Bool) -> Void)?, onCanGoForward: ((Bool) -> Void)?, onContentType: ((String) -> Void)?, + onNavigationError: ((Error?) -> Void)?, allowedDomains: [String]? = nil, settings: AsyncHeadlessWebViewSettings = AsyncHeadlessWebViewSettings()) { self.parent = parent @@ -60,6 +62,7 @@ final class HeadlessWebViewCoordinator: NSObject { self.onURLChange = onURLChange self.onCanGoBack = onCanGoBack self.onCanGoForward = onCanGoForward + self.onNavigationError = onNavigationError self.onContentType = onContentType self.settings = settings } @@ -106,6 +109,7 @@ final class HeadlessWebViewCoordinator: NSObject { onCanGoBack = nil onCanGoForward = nil onContentType = nil + onNavigationError = nil } } @@ -128,6 +132,7 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { } func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { + onNavigationError?(nil) if let url = webView.url, url != lastURL { onURLChange?(url) lastURL = url @@ -182,8 +187,12 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { decisionHandler(.allow) } + func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: any Error) { + onNavigationError?(error) + } + func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - // NOOP + onNavigationError?(error) } // Javascript Confirm dialogs Delegate diff --git a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift index 276ef14926..a577a61613 100644 --- a/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/IdentityTheftRestorationPagesFeature.swift @@ -66,10 +66,13 @@ final class IdentityTheftRestorationPagesFeature: Subfeature, ObservableObject { return nil } } - + func getAccessToken(params: Any, original: WKScriptMessage) async throws -> Encodable? { - let authToken = AccountManager().authToken ?? "" - return [Constants.token: authToken] + if let accessToken = AccountManager().accessToken { + return [Constants.token: accessToken] + } else { + return [String: String]() + } } deinit { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 6cb2ea102f..8a5874fff9 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -38,6 +38,7 @@ final class SubscriptionEmailViewModel: ObservableObject { @Published var activateSubscription = false @Published var managingSubscriptionEmail = false @Published var transactionError: SubscriptionRestoreError? + @Published var navigationError: Bool = false @Published var shouldDisplayInactiveError: Bool = false var webViewModel: AsyncHeadlessWebViewViewModel @@ -101,6 +102,17 @@ final class SubscriptionEmailViewModel: ObservableObject { } } .store(in: &cancellables) + + webViewModel.$navigationError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let strongSelf = self else { return } + DispatchQueue.main.async { + strongSelf.navigationError = error != nil ? true : false + } + + } + .store(in: &cancellables) } private func handleTransactionError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 6d0cb332d7..295b22e45b 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -224,10 +224,24 @@ final class SubscriptionFlowViewModel: ObservableObject { } .store(in: &cancellables) + webViewModel.$navigationError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let strongSelf = self else { return } + DispatchQueue.main.async { + strongSelf.transactionError = error != nil ? .generalError : nil + } + + } + .store(in: &cancellables) + canGoBackCancellable = webViewModel.$canGoBack .receive(on: DispatchQueue.main) .sink { [weak self] value in - self?.canNavigateBack = value + guard let strongSelf = self else { return } + + let shouldNavigateBack = value && (strongSelf.webViewModel.url?.lastPathComponent != URL.subscriptionBaseURL.lastPathComponent) + strongSelf.canNavigateBack = shouldNavigateBack } } @@ -242,6 +256,12 @@ final class SubscriptionFlowViewModel: ObservableObject { canNavigateBack = false } + private func urlRemovingQueryParams(_ url: URL) -> URL? { + var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) + urlComponents?.query = nil // Remove the query string + return urlComponents?.url + } + func initializeViewData() async { await self.setupTransactionObserver() await self .setupWebViewObservers() @@ -257,6 +277,7 @@ final class SubscriptionFlowViewModel: ObservableObject { userTappedRestoreButton = false shouldShowNavigationBar = false selectedFeature = nil + transactionError = nil canNavigateBack = false shouldDismissView = true subFeature.cleanup() diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 6e613491a8..99694a5468 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -46,6 +46,7 @@ final class SubscriptionITPViewModel: ObservableObject { @Published var isDownloadableContent: Bool = false @Published var activityItems: [Any] = [] @Published var attachmentURL: URL? + @Published var navigationError: Bool = false var webViewModel: AsyncHeadlessWebViewViewModel @Published var shouldNavigateToExternalURL: URL? @@ -81,9 +82,20 @@ final class SubscriptionITPViewModel: ObservableObject { settings: webViewSettings) } - // Observe transaction status + // swiftlint:disable function_body_length private func setupSubscribers() async { + webViewModel.$navigationError + .receive(on: DispatchQueue.main) + .sink { [weak self] error in + guard let strongSelf = self else { return } + DispatchQueue.main.async { + strongSelf.navigationError = error != nil ? true : false + } + + } + .store(in: &cancellables) + webViewModel.$scrollPosition .receive(on: DispatchQueue.main) .throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true) @@ -134,6 +146,7 @@ final class SubscriptionITPViewModel: ObservableObject { self?.canNavigateBack = value } } + // swiftlint:enable function_body_length func initializeView() { webViewModel.navigationCoordinator.navigateTo(url: manageITPURL ) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 3da604761f..cbeee2fb26 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -81,7 +81,7 @@ final class SubscriptionRestoreViewModel: ObservableObject { private func handleRestoreError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { switch error { case .failedToRestorePastPurchase: - activationResult = .notFound + activationResult = .error case .subscriptionExpired: activationResult = .expired case .subscriptionNotFound: diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 8712560f36..a8b3d72c83 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -50,6 +50,15 @@ struct SubscriptionEmailView: View { ) } + .alert(isPresented: $viewModel.navigationError) { + Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + dismiss() + }) + } + .onAppear { viewModel.loadURL() } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 85e820fd3a..f611c5ead8 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -31,7 +31,8 @@ struct SubscriptionFlowView: View { @State private var shouldShowNavigationBar = false @State private var isActive = false @State private var transactionError: SubscriptionFlowViewModel.SubscriptionPurchaseError? - @State private var shouldPresentError = false + @State private var errorMessage: SubscriptionErrorMessage = .general + @State private var shouldPresentError: Bool = false @State private var isFirstOnAppear = true enum Constants { @@ -42,6 +43,13 @@ struct SubscriptionFlowView: View { static let backButtonImage = "chevron.left" } + enum SubscriptionErrorMessage { + case activeSubscription + case appStore + case backend + case general + } + var body: some View { NavigationView { baseView @@ -134,10 +142,28 @@ struct SubscriptionFlowView: View { } .onChange(of: viewModel.transactionError) { value in - if value != nil { - shouldPresentError = true + + if !shouldPresentError { + let displayError: Bool = { + switch value { + case .hasActiveSubscription: + errorMessage = .activeSubscription + return true + case .failedToRestorePastPurchase, .purchaseFailed: + errorMessage = .appStore + return true + case .failedToGetSubscriptionOptions, .generalError: + errorMessage = .backend + return true + default: + return false + } + }() + + if displayError { + shouldPresentError = true + } } - transactionError = value } .onAppear(perform: { @@ -165,7 +191,8 @@ struct SubscriptionFlowView: View { }) .alert(isPresented: $shouldPresentError) { - getAlert() + getAlert(error: self.errorMessage) + } // The trailing close button should be hidden when a transaction is in progress @@ -173,12 +200,12 @@ struct SubscriptionFlowView: View { ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } : nil) } - - private func getAlert() -> Alert { - switch transactionError { - case .hasActiveSubscription: - Alert( + private func getAlert(error: SubscriptionErrorMessage) -> Alert { + + switch error { + case .activeSubscription: + return Alert( title: Text(UserText.subscriptionFoundTitle), message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { @@ -188,14 +215,23 @@ struct SubscriptionFlowView: View { viewModel.restoreAppstoreTransaction() } ) - default: - Alert( + case .appStore: + return Alert( title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage), dismissButton: .cancel(Text(UserText.actionOK)) { Task { await viewModel.initializeViewData() } } ) + case .backend, .general: + return Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + viewModel.finalizeSubscriptionFlow() + dismiss() + } + ) } } @@ -203,8 +239,14 @@ struct SubscriptionFlowView: View { private var webView: some View { ZStack(alignment: .top) { - // Restore View Hidden Link - NavigationLink(destination: SubscriptionRestoreView(), isActive: $isActive) { + + // Restore View Hidden Link + let restoreView = SubscriptionRestoreView( + onDismissStack: { + viewModel.finalizeSubscriptionFlow() + dismiss() + }) + NavigationLink(destination: restoreView, isActive: $isActive) { EmptyView() }.isDetailLink(false) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 32f0f60d0e..96e2653cf6 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -85,6 +85,17 @@ struct SubscriptionITPView: View { } .tint(Color(designSystemColor: .textPrimary)) + + .alert(isPresented: $viewModel.navigationError) { + Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + dismiss() + }) + } + + .sheet(isPresented: Binding( get: { viewModel.shouldShowExternalURLSheet }, set: { if !$0 { viewModel.shouldNavigateToExternalURL = nil } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 7185163b3f..d6b1d7cf6a 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -32,6 +32,7 @@ struct SubscriptionRestoreView: View { @State private var expandedItemId: Int = 0 @State private var isAlertVisible = false @State private var isActive: Bool = false + var onDismissStack: (() -> Void)? private enum Constants { static let heroImage = "ManageSubscriptionHero" @@ -273,10 +274,15 @@ struct SubscriptionRestoreView: View { dismiss() }), secondaryButton: .cancel()) - case .error: - return Alert(title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage)) default: - return Alert(title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage)) + return Alert( + title: Text(UserText.subscriptionBackendErrorTitle), + message: Text(UserText.subscriptionBackendErrorMessage), + dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { + onDismissStack?() + dismiss() + } + ) } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 3aeeba2b15..c7cf2b241d 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1135,6 +1135,10 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionAppStoreErrorTitle = NSLocalizedString("subscription.restore.general.error.title", value: "Something Went Wrong", comment: "Alert for general error title") public static let subscriptionAppStoreErrorMessage = NSLocalizedString("subscription.restore.general.error.message", value: "The App Store was unable to process your purchase. Please try again later.", comment: "Alert for general error message") + public static let subscriptionBackendErrorTitle = NSLocalizedString("subscription.restore.backend.error.title", value: "Something Went Wrong", comment: "Alert for general error title") + public static let subscriptionBackendErrorMessage = NSLocalizedString("subscription.restore.backend.error.message", value: "We’re having trouble connecting. Please try again later.", comment: "Alert for general error message") + public static let subscriptionBackendErrorButton = NSLocalizedString("subscription.restore.backend.error.button", value: "Back to Settings", comment: "Button text for general error message") + // PIR: public static let subscriptionPIRHeroText = NSLocalizedString("subscription.pir.hero", value: "Activate Privacy Pro on desktop to set up Personal Information Removal", comment: "Hero Text for Personal information removal") public static let subscriptionPIRHeroDetail = NSLocalizedString("subscription.pir.heroText", value: "In the DuckDuckGo browser for desktop, go to %@ and click %@ to get started.", comment: "Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. ") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index cf0aa4ca37..400655f967 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2151,6 +2151,15 @@ But if you *do* want a peek under the hood, you can find more information about /* text for renewal string */ "subscription.renews" = "renews"; +/* Button text for general error message */ +"subscription.restore.backend.error.button" = "Back to Settings"; + +/* Alert for general error message */ +"subscription.restore.backend.error.message" = "We’re having trouble connecting. Please try again later."; + +/* Alert for general error title */ +"subscription.restore.backend.error.title" = "Something Went Wrong"; + /* Alert for general error message */ "subscription.restore.general.error.message" = "The App Store was unable to process your purchase. Please try again later."; diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 40ce86837d..6b7ad1e7f1 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 40ce86837def0adbf558f00ed0531ab4df5839a8 +Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 From 454fb216dadbcb3f3380e28db226dbd906314e03 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Mar 2024 23:56:15 +0100 Subject: [PATCH 113/245] Subscriptions: 20 - Subscription Caching (#2569) Task/Issue URL: https://app.asana.com/0/0/1206800657723184/f BSK PR: duckduckgo/BrowserServicesKit#710 Description: Bumps BSK to use subscription caching: Push Purchase Cancel update to Webview on user cancellation Minor presentation updates for sheets and subscription-related Settings cleanup Co-authored-by: Michal Smaga --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/SettingsSubscriptionView.swift | 32 ++++++-------- DuckDuckGo/SettingsViewModel.swift | 44 ++++++++----------- ...scriptionPagesUseSubscriptionFeature.swift | 2 + .../SubscriptionSettingsViewModel.swift | 32 ++++++-------- submodules/privacy-reference-tests | 2 +- 7 files changed, 51 insertions(+), 67 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 891f03a26f..e1800adb4f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10044,7 +10044,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 122.0.0; + version = 122.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index faaf2e1be7..fde59987a6 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" : "83bcbbf0dace717db6e518e4d867d617c846a3b5", - "version" : "122.0.0" + "revision" : "4042a8e04396584566df24fb90c7b529bbe1c661", + "version" : "122.1.0" } }, { diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index a727b87dc0..846e20893a 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -94,6 +94,11 @@ struct SettingsSubscriptionView: View { action: { isShowingsubScriptionFlow = true }, isButton: true ) + // Subscription Restore + .sheet(isPresented: $isShowingsubScriptionFlow, + onDismiss: { Task { viewModel.onAppear() } }, + content: { SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() }) + SettingsCustomCell(content: { iHaveASubscriptionView }, action: { isShowingsubScriptionFlow = true @@ -139,6 +144,10 @@ struct SettingsSubscriptionView: View { subtitle: UserText.settingsPProDBPSubTitle, action: { isShowingDBP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingDBP) { + SubscriptionPIRView() + } + } if viewModel.shouldShowITP { @@ -146,6 +155,10 @@ struct SettingsSubscriptionView: View { subtitle: UserText.settingsPProITRSubTitle, action: { isShowingITP.toggle() }, isButton: true) + .sheet(isPresented: $isShowingITP) { + SubscriptionITPView() + } + } NavigationLink(destination: SubscriptionSettingsView()) { @@ -153,12 +166,7 @@ struct SettingsSubscriptionView: View { } } - .sheet(isPresented: $isShowingDBP) { - SubscriptionPIRView() - } - .sheet(isPresented: $isShowingITP) { - SubscriptionITPView() - } + } var body: some View { @@ -181,18 +189,6 @@ struct SettingsSubscriptionView: View { } } - // Subscription Restore - .sheet(isPresented: $isShowingsubScriptionFlow) { - SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() - } - - - // Refresh subscription when dismissing the Subscription Flow - .onChange(of: isShowingsubScriptionFlow, perform: { value in - if !value { - Task { viewModel.onAppear() } - } - }) .onChange(of: viewModel.shouldNavigateToDBP, perform: { value in if value { diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 7aaba476b6..f67e383194 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -248,8 +248,9 @@ extension SettingsViewModel { // This manual (re)initialization will go away once appSettings and // other dependencies are observable (Such as AppIcon and netP) // and we can use subscribers (Currently called from the view onAppear) - private func initState() { - self.state = SettingsState( + @MainActor + private func initState() async { + self.state = await SettingsState( appTheme: appSettings.currentThemeName, appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: appSettings.currentFireButtonAnimation, @@ -275,15 +276,6 @@ extension SettingsViewModel { setupSubscribers() - #if SUBSCRIPTION - if #available(iOS 15, *) { - Task { - if state.subscription.enabled { - await setupSubscriptionEnvironment() - } - } - } - #endif } private func getNetworkProtectionState() -> SettingsState.NetworkProtection { @@ -297,14 +289,24 @@ extension SettingsViewModel { return SettingsState.NetworkProtection(enabled: enabled, status: "") } - private func getSubscriptionState() -> SettingsState.Subscription { + private func getSubscriptionState() async -> SettingsState.Subscription { var enabled = false var canPurchase = false - let hasActiveSubscription = Self.cachedHasActiveSubscription - #if SUBSCRIPTION + var hasActiveSubscription = false + +#if SUBSCRIPTION + if #available(iOS 15, *) { enabled = featureFlagger.isFeatureOn(.subscription) canPurchase = SubscriptionPurchaseEnvironment.canPurchase - #endif + await setupSubscriptionEnvironment() + if let token = AccountManager().accessToken { + let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token) + if case .success(let subscription) = subscriptionResult { + hasActiveSubscription = subscription.isActive + } + } + } +#endif return SettingsState.Subscription(enabled: enabled, canPurchase: canPurchase, hasActiveSubscription: hasActiveSubscription) @@ -354,9 +356,6 @@ extension SettingsViewModel { case .success(let subscription) where subscription.isActive: - // Cache Subscription state - cacheSubscriptionState(active: true) - // Check entitlements and update UI accordingly let entitlements: [Entitlement.ProductName] = [.identityTheftRestoration, .dataBrokerProtection, .networkProtection] for entitlement in entitlements { @@ -382,18 +381,11 @@ extension SettingsViewModel { @available(iOS 15.0, *) private func signOutUser() { AccountManager().signOut() - cacheSubscriptionState(active: false) setupSubscriptionPurchaseOptions() } - private func cacheSubscriptionState(active: Bool) { - self.state.subscription.hasActiveSubscription = active - Self.cachedHasActiveSubscription = active - } - @available(iOS 15.0, *) private func setupSubscriptionPurchaseOptions() { - cacheSubscriptionState(active: false) PurchaseManager.shared.$availableProducts .receive(on: RunLoop.main) .sink { [weak self] products in @@ -470,7 +462,7 @@ extension SettingsViewModel { extension SettingsViewModel { func onAppear() { - initState() + Task { await initState() } Task { await MainActor.run { navigateOnAppear() } } } diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 8ad92a96ca..f2d5a01c27 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -225,6 +225,8 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec switch error { case .cancelledByUser: setTransactionError(.cancelledByUser) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) + return nil case .accountCreationFailed: setTransactionError(.accountCreationFailed) case .activeSubscriptionAlreadyPresent: diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 5c65f88e98..102a365523 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -56,24 +56,16 @@ final class SubscriptionSettingsViewModel: ObservableObject { }() @MainActor - func fetchAndUpdateSubscriptionDetails() { + func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad) { Task { - guard let token = accountManager.accessToken else { return } - - if let cachedDate = SubscriptionService.cachedGetSubscriptionResponse?.expiresOrRenewsAt, - let cachedStatus = SubscriptionService.cachedGetSubscriptionResponse?.status, - let productID = SubscriptionService.cachedGetSubscriptionResponse?.productId { - updateSubscriptionDetails(status: cachedStatus, date: cachedDate, product: productID) - } - - if case .success(let subscription) = await SubscriptionService.getSubscription(accessToken: token) { - if !subscription.isActive { - AccountManager().signOut() - shouldDismissView = true - return - } else { - updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId) - } + guard let token = self.accountManager.accessToken else { return } + let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) + switch subscriptionResult { + case .success(let subscription): + updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId) + case .failure(let error): + AccountManager().signOut() + shouldDismissView = true } } } @@ -86,11 +78,13 @@ final class SubscriptionSettingsViewModel: ObservableObject { } } + // Re-fetch subscription from server ignoring cache + // This ensure that if the user changed something on the Apple view, state will be updated private func setupSubscriptionUpdater() { subscriptionUpdateTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { [weak self] _ in guard let strongSelf = self else { return } Task { - await strongSelf.fetchAndUpdateSubscriptionDetails() + await strongSelf.fetchAndUpdateSubscriptionDetails(cachePolicy: .reloadIgnoringLocalCacheData) } } } @@ -104,7 +98,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { func removeSubscription() { AccountManager().signOut() - let messageView = ActionMessageView() + _ = ActionMessageView() ActionMessageView.present(message: UserText.subscriptionRemovalConfirmation, presentationLocation: .withoutBottomBar) } diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..a603ff9af2 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit a603ff9af22ca3ff7ce2e7ffbfe18c447d9f23e8 From f01fef39d522d40ed5471296e054a620dd8dd68a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 13 Mar 2024 00:19:02 +0100 Subject: [PATCH 114/245] Updates BSK (#2577) Task/Issue URL: https://app.asana.com/0/1199230911884351/1206800069675113/f macOS: https://github.com/duckduckgo/macos-browser/pull/2369 BSK: https://github.com/duckduckgo/BrowserServicesKit/pull/716 ## Description Fixes the following: - Several users seem to be no longer rekeying in the latest releases. - Some users seem to have trouble connecting due to these rekeying issues. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 873ec456d0..40e4e46463 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10029,7 +10029,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 121.0.0; + version = "121.0.0-1"; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 1632d16407..bbc745fd86 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" : "4555c3dbf265f1dca0304c69e7013b9d46a758b3", - "version" : "121.0.0" + "revision" : "592242549bc2258d5ebeb3224029267915bcca52", + "version" : "121.0.0-1" } }, { From 7932517ce50ad79e7500dda42c15b700af4c15d9 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 12 Mar 2024 16:52:21 -0700 Subject: [PATCH 115/245] Release 7.112.0-2 (#2579) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 40e4e46463..e2d199b74c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8258,7 +8258,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8295,7 +8295,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8387,7 +8387,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8415,7 +8415,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8565,7 +8565,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8591,7 +8591,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8656,7 +8656,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8691,7 +8691,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8725,7 +8725,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8756,7 +8756,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9043,7 +9043,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9074,7 +9074,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9103,7 +9103,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9137,7 +9137,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9168,7 +9168,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9201,11 +9201,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9439,7 +9439,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9466,7 +9466,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9499,7 +9499,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9537,7 +9537,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9573,7 +9573,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9608,11 +9608,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9786,11 +9786,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9819,10 +9819,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 40c02ec751becda5ff8758ca8d4d45ef03c5b878 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 13 Mar 2024 21:49:19 +0600 Subject: [PATCH 116/245] rollback #URL macro (#2585) Task/Issue URL: https://app.asana.com/0/414235014887631/1206830008080445/f BSK PR: duckduckgo/BrowserServicesKit#718 macOS PR: duckduckgo/macos-browser#2403 --- Core/AppURLs.swift | 3 +- Core/BookmarksImporter.swift | 3 +- Core/DataStoreWarmup.swift | 3 +- Core/UserAgentManager.swift | 3 +- DuckDuckGo.xcodeproj/project.pbxproj | 66 +------------------ .../xcshareddata/swiftpm/Package.resolved | 4 +- .../AutofillLoginDetailsViewModel.swift | 3 +- DuckDuckGo/DesktopDownloadViewModel.swift | 3 +- DuckDuckGo/FirewallManager.swift | 3 +- DuckDuckGo/RemoteMessageRequest.swift | 5 +- .../AddressDisplayHelperTests.swift | 31 +++++---- DuckDuckGoTests/AppURLsTests.swift | 43 ++++++------ .../BookmarksCachingSearchTests.swift | 11 ++-- DuckDuckGoTests/BookmarksImporterTests.swift | 3 +- DuckDuckGoTests/DaxDialogTests.swift | 15 ++--- DuckDuckGoTests/DownloadMocks.swift | 3 +- DuckDuckGoTests/DownloadTestsHelper.swift | 3 +- .../FaviconRequestModifierTests.swift | 3 +- DuckDuckGoTests/FaviconsTests.swift | 5 +- .../FireproofFaviconUpdaterTests.swift | 5 +- DuckDuckGoTests/HTTPSUpgradeTests.swift | 11 ++-- .../MenuBookmarksViewModelTests.swift | 3 +- DuckDuckGoTests/MockSecureVault.swift | 5 +- DuckDuckGoTests/MockUserAgent.swift | 3 +- .../NotFoundCachingDownloaderTests.swift | 7 +- DuckDuckGoTests/PrivacyIconLogicTests.swift | 11 ++-- DuckDuckGoTests/TabTests.swift | 7 +- DuckDuckGoTests/TabsModelTests.swift | 11 ++-- .../TrackerAnimationLogicTests.swift | 3 +- DuckDuckGoTests/UserAgentTests.swift | 11 ++-- LocalPackages/Waitlist/Package.swift | 1 - .../Network/ProductWaitlistRequest.swift | 5 +- .../Sources/WaitlistMocks/TestWaitlist.swift | 3 +- .../WaitlistViewModelTests.swift | 3 +- Widgets/WidgetViews.swift | 3 +- submodules/privacy-reference-tests | 2 +- 36 files changed, 105 insertions(+), 202 deletions(-) diff --git a/Core/AppURLs.swift b/Core/AppURLs.swift index eff05031d3..a1c6b1c9d2 100644 --- a/Core/AppURLs.swift +++ b/Core/AppURLs.swift @@ -19,7 +19,6 @@ import BrowserServicesKit import Foundation -import Macros public extension URL { @@ -49,7 +48,7 @@ public extension URL { static let exti = URL(string: "\(base)/exti/\(devMode)")! static let feedback = URL(string: "\(base)/feedback.js?type=app-feedback")! - static let appStore = #URL("https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361") + static let appStore = URL(string: "https://apps.apple.com/app/duckduckgo-privacy-browser/id663592361")! static let mac = URL(string: "\(base)/mac")! static let windows = URL(string: "\(base)/windows")! diff --git a/Core/BookmarksImporter.swift b/Core/BookmarksImporter.swift index 4615e47689..b2398fa173 100644 --- a/Core/BookmarksImporter.swift +++ b/Core/BookmarksImporter.swift @@ -20,7 +20,6 @@ import Bookmarks import Common import Foundation -import Macros import Persistence import SwiftSoup @@ -218,7 +217,7 @@ final public class BookmarksImporter { static let FavoritesFolder = "DuckDuckGo Favorites" static let BookmarksFolder = "DuckDuckGo Bookmarks" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = #URL("https://duckduckgo.com") + static let bookmarkURL = URL(string: "https://duckduckgo.com")! static let favoriteAttribute = "duckduckgo:favorite" static let isFavorite = "true" static let idAttribute = "id" diff --git a/Core/DataStoreWarmup.swift b/Core/DataStoreWarmup.swift index 6554f0ca8c..79087b9fc2 100644 --- a/Core/DataStoreWarmup.swift +++ b/Core/DataStoreWarmup.swift @@ -18,7 +18,6 @@ // import Combine -import Macros import WebKit /// WKWebsiteDataStore is basically non-functional until a web view has been instanciated and a page is successfully loaded. @@ -28,7 +27,7 @@ public class DataStoreWarmup { @MainActor public func ensureReady() async { - await BlockingNavigationDelegate().loadInBackgroundWebView(url: #URL("about:blank")) + await BlockingNavigationDelegate().loadInBackgroundWebView(url: URL(string: "about:blank")!) } } diff --git a/Core/UserAgentManager.swift b/Core/UserAgentManager.swift index 6ecc0c7013..9c9225e6eb 100644 --- a/Core/UserAgentManager.swift +++ b/Core/UserAgentManager.swift @@ -22,7 +22,6 @@ import BrowserServicesKit import Common import Foundation -import Macros import WebKit public protocol UserAgentManager { @@ -47,7 +46,7 @@ public class DefaultUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(#URL("about:blank"))) + webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned _ = webview diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2c273541d6..292438d4d1 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -689,14 +689,6 @@ B652DF10287C2C1600C12A9C /* ContentBlocking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9847BFFD27A2DDB400DB07AA /* ContentBlocking.swift */; }; B652DF12287C336E00C12A9C /* ContentBlockingUpdating.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DF11287C336E00C12A9C /* ContentBlockingUpdating.swift */; }; B652DF13287C373A00C12A9C /* ScriptSourceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = B652DEFE287BF1FE00C12A9C /* ScriptSourceProviding.swift */; }; - B6A26C042B98358B00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C032B98358B00DF9EAD /* Macros */; }; - B6A26C062B98359A00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C052B98359A00DF9EAD /* Macros */; }; - B6A26C082B9835A000DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C072B9835A000DF9EAD /* Macros */; }; - B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C092B9835A800DF9EAD /* Macros */; }; - B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0B2B9835AD00DF9EAD /* Macros */; }; - B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0D2B9835B100DF9EAD /* Macros */; }; - B6A26C102B9835B400DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C0F2B9835B400DF9EAD /* Macros */; }; - B6A26C122B9835B800DF9EAD /* Macros in Frameworks */ = {isa = PBXBuildFile; productRef = B6A26C112B9835B800DF9EAD /* Macros */; }; B6AD9E3628D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3428D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift */; }; B6AD9E3728D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AD9E3528D4510A0019CDE9 /* ContentBlockingUpdatingTests.swift */; }; B6AD9E3828D4512E0019CDE9 /* EmbeddedTrackerDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9801F08927E4B21100191874 /* EmbeddedTrackerDataTests.swift */; }; @@ -2709,7 +2701,6 @@ 0202569029881ECA00E694E7 /* CocoaAsyncSocket in Frameworks */, 02025664298818B200E694E7 /* NetworkExtension.framework in Frameworks */, 4B470EE4299C6DFB0086EBDC /* Core.framework in Frameworks */, - B6A26C062B98359A00DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2717,7 +2708,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B6A26C0E2B9835B100DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2736,7 +2726,6 @@ 853273B624FFE0BB00E3C778 /* WidgetKit.framework in Frameworks */, 0238E44F29C0FAA100615E30 /* FindInPageIOSJSSupport in Frameworks */, 3760DFED299315EF0045A446 /* Waitlist in Frameworks */, - B6A26C042B98358B00DF9EAD /* Macros in Frameworks */, F1D43AFA2B99C1D300BAB743 /* BareBonesBrowserKit in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */, @@ -2754,7 +2743,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B6A26C0A2B9835A800DF9EAD /* Macros in Frameworks */, F486D3362506A037002D07D7 /* OHHTTPStubs in Frameworks */, F486D3382506A225002D07D7 /* OHHTTPStubsSwift in Frameworks */, 4BE67B052B96B9AB007335F7 /* ContentBlocking in Frameworks */, @@ -2787,7 +2775,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B6A26C102B9835B400DF9EAD /* Macros in Frameworks */, 1E1D8B632995143200C96994 /* OHHTTPStubs in Frameworks */, 1E1D8B652995143200C96994 /* OHHTTPStubsSwift in Frameworks */, 4BE67B012B96B741007335F7 /* Common in Frameworks */, @@ -2799,7 +2786,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - B6A26C0C2B9835AD00DF9EAD /* Macros in Frameworks */, F486D31D2506980E002D07D7 /* Swifter in Frameworks */, 85F21DC021123B03002631A6 /* Core.framework in Frameworks */, ); @@ -2810,7 +2796,6 @@ buildActionMask = 2147483647; files = ( 98D4B7DF2944DDBD0068814D /* Core.framework in Frameworks */, - B6A26C122B9835B800DF9EAD /* Macros in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2819,7 +2804,6 @@ buildActionMask = 2147483647; files = ( 4B948E2629DCCDB9002531FA /* Persistence in Frameworks */, - B6A26C082B9835A000DF9EAD /* Macros in Frameworks */, 98A50962294B48A400D10880 /* Bookmarks in Frameworks */, 1E60989B290009C700A508F9 /* Common in Frameworks */, 1E60989D290011E600A508F9 /* ContentBlocking in Frameworks */, @@ -5701,7 +5685,6 @@ name = PacketTunnelProvider; packageProductDependencies = ( 0202568F29881ECA00E694E7 /* CocoaAsyncSocket */, - B6A26C052B98359A00DF9EAD /* Macros */, ); productName = PacketTunnelProvider; productReference = 02025662298818B100E694E7 /* PacketTunnelProvider.appex */; @@ -5723,7 +5706,6 @@ ); name = FingerprintingUITests; packageProductDependencies = ( - B6A26C0D2B9835B100DF9EAD /* Macros */, ); productName = FingerprintingUITests; productReference = 025CCFE22582601C001CD5BB /* FingerprintingUITests.xctest */; @@ -5785,7 +5767,6 @@ 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, 4B2754EB29E8C7DF00394032 /* Lottie */, CB941A6D2B96AB08000F9E7A /* PrivacyDashboard */, - B6A26C032B98358B00DF9EAD /* Macros */, F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */, ); productName = DuckDuckGo; @@ -5814,7 +5795,6 @@ F115ED9B2B4EFC8E001A0453 /* TestUtils */, 4BE67B042B96B9AB007335F7 /* ContentBlocking */, 4BE67B062B96B9B0007335F7 /* Common */, - B6A26C092B9835A800DF9EAD /* Macros */, ); productName = DuckDuckGoTests; productReference = 84E341A61E2F7EFB00BDBA6F /* UnitTests.xctest */; @@ -5880,7 +5860,6 @@ 1E1D8B642995143200C96994 /* OHHTTPStubsSwift */, 4BE67B002B96B741007335F7 /* Common */, 4BE67B022B96B864007335F7 /* ContentBlocking */, - B6A26C0F2B9835B400DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 85D33FCB25C97B6E002B91A6 /* IntegrationTests.xctest */; @@ -5903,7 +5882,6 @@ name = AtbUITests; packageProductDependencies = ( F486D31C2506980E002D07D7 /* Swifter */, - B6A26C0B2B9835AD00DF9EAD /* Macros */, ); productName = AtbIntegrationTests; productReference = 85F21DAD210F5E32002631A6 /* AtbUITests.xctest */; @@ -5925,7 +5903,6 @@ ); name = PerformanceTests; packageProductDependencies = ( - B6A26C112B9835B800DF9EAD /* Macros */, ); productName = IntegrationTests; productReference = 9825F9D7293F2DE900F220F2 /* PerformanceTests.xctest */; @@ -5978,7 +5955,6 @@ D61CDA152B7CF77300A0FBB9 /* Subscription */, D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, 858D009C2B9799FC004E5B4C /* History */, - B6A26C072B9835A000DF9EAD /* Macros */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -10044,7 +10020,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 122.1.1; + version = 122.2.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10231,46 +10207,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; - B6A26C032B98358B00DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C052B98359A00DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C072B9835A000DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C092B9835A800DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C0B2B9835AD00DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C0D2B9835B100DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C0F2B9835B400DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; - B6A26C112B9835B800DF9EAD /* Macros */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = Macros; - }; B6F997CB2B8F380A00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e49c7121a7..dc42e87ea6 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" : "8da10a2d3cbd3ca3284898896a0471d024a155ec", - "version" : "122.1.1" + "revision" : "4e05a46f0a9ce56f6d6379b79a92dc7a0182e027", + "version" : "122.2.0" } }, { diff --git a/DuckDuckGo/AutofillLoginDetailsViewModel.swift b/DuckDuckGo/AutofillLoginDetailsViewModel.swift index 5aacbeea91..aff24b8c58 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewModel.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewModel.swift @@ -25,7 +25,6 @@ import Core import DDGSync import DesignResourcesKit import Foundation -import Macros import SecureStorage import SwiftUI @@ -59,7 +58,7 @@ final class AutofillLoginDetailsViewModel: ObservableObject { } enum Constants { - static let privateEmailURL = #URL("https://duckduckgo.com/email") + static let privateEmailURL = URL(string: "https://duckduckgo.com/email")! } weak var delegate: AutofillLoginDetailsViewModelDelegate? diff --git a/DuckDuckGo/DesktopDownloadViewModel.swift b/DuckDuckGo/DesktopDownloadViewModel.swift index 9a14e3b360..6d52883345 100644 --- a/DuckDuckGo/DesktopDownloadViewModel.swift +++ b/DuckDuckGo/DesktopDownloadViewModel.swift @@ -18,12 +18,11 @@ // import Foundation -import Macros import UIKit final class DesktopDownloadViewModel: ObservableObject { - static let defaultURL = #URL("https://duckduckgo.com/") + static let defaultURL = URL(string: "https://duckduckgo.com/")! static let prefix = "https://" private var platform: DesktopDownloadPlatform diff --git a/DuckDuckGo/FirewallManager.swift b/DuckDuckGo/FirewallManager.swift index 0035b6161c..bc3e51b344 100644 --- a/DuckDuckGo/FirewallManager.swift +++ b/DuckDuckGo/FirewallManager.swift @@ -23,7 +23,6 @@ import Foundation import NetworkExtension import BrowserServicesKit import Common -import Macros public protocol FirewallDelegate: AnyObject { func statusDidChange(newStatus: NEVPNStatus) @@ -81,7 +80,7 @@ public class FirewallManager: FirewallManaging { config.requestCachePolicy = .reloadIgnoringLocalCacheData config.urlCache = nil let session = URLSession(configuration: config) - let url = #URL("https://bad_url") + let url = URL(string: "https://bad_url")! os_log("[INFO] Calling dummy URL to force VPN", log: FirewallManager.apptpLog, type: .debug) _ = try? await session.data(from: url) diff --git a/DuckDuckGo/RemoteMessageRequest.swift b/DuckDuckGo/RemoteMessageRequest.swift index 2aa0192467..c52ad8cb7a 100644 --- a/DuckDuckGo/RemoteMessageRequest.swift +++ b/DuckDuckGo/RemoteMessageRequest.swift @@ -20,7 +20,6 @@ import BrowserServicesKit import Core import Foundation -import Macros import Networking import RemoteMessaging @@ -28,9 +27,9 @@ public struct RemoteMessageRequest { private var endpoint: URL { #if DEBUG - return #URL("https://raw.githubusercontent.com/duckduckgo/remote-messaging-config/main/samples/ios/sample1.json") + return URL(string: "https://raw.githubusercontent.com/duckduckgo/remote-messaging-config/main/samples/ios/sample1.json")! #else - return #URL("https://staticcdn.duckduckgo.com/remotemessaging/config/v1/ios-config.json") + return URL(string: "https://staticcdn.duckduckgo.com/remotemessaging/config/v1/ios-config.json")! #endif } diff --git a/DuckDuckGoTests/AddressDisplayHelperTests.swift b/DuckDuckGoTests/AddressDisplayHelperTests.swift index e9fae0c49e..5aa4108aa2 100644 --- a/DuckDuckGoTests/AddressDisplayHelperTests.swift +++ b/DuckDuckGoTests/AddressDisplayHelperTests.swift @@ -17,7 +17,6 @@ // limitations under the License. // -import Macros import XCTest @testable import DuckDuckGo @@ -28,36 +27,36 @@ class AddressDisplayHelperTests: XCTestCase { func testShortURL() { - XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.duckduckgo.com")), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.duckduckgo.com/some/path")), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.subdomain.duckduckgo.com/some/path")), "subdomain.duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(#URL("https://m.duckduckgo.com/some/path")), "m.duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(#URL("http://some-other.sub.domain.duck.eu/with/path")), "some-other.sub.domain.duck.eu") - XCTAssertEqual(AddressHelper.shortURLString(#URL("http://duckduckgo.com:1234")), "duckduckgo.com") - XCTAssertEqual(AddressHelper.shortURLString(#URL("https://192.168.0.1:1234")), "192.168.0.1") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com")!), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.duckduckgo.com/some/path")!), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.subdomain.duckduckgo.com/some/path")!), "subdomain.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://m.duckduckgo.com/some/path")!), "m.duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://some-other.sub.domain.duck.eu/with/path")!), "some-other.sub.domain.duck.eu") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "http://duckduckgo.com:1234")!), "duckduckgo.com") + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://192.168.0.1:1234")!), "192.168.0.1") - XCTAssertEqual(AddressHelper.shortURLString(#URL("https://www.com")), "com") // This is an exception we are ok with) + XCTAssertEqual(AddressHelper.shortURLString(URL(string: "https://www.com")!), "com") // This is an exception we are ok with) - XCTAssertNil(AddressHelper.shortURLString(#URL("file:///some/path"))) - XCTAssertNil(AddressHelper.shortURLString(#URL("somescheme:///some/path"))) - XCTAssertNil(AddressHelper.shortURLString(#URL("blob:https://www.my.com/111-222-333-444"))) - XCTAssertNil(AddressHelper.shortURLString(#URL("data:text/plain;charset=UTF-8;page=21,the%20data:12345"))) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "file:///some/path")!)) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "somescheme:///some/path")!)) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "blob:https://www.my.com/111-222-333-444")!)) + XCTAssertNil(AddressHelper.shortURLString(URL(string: "data:text/plain;charset=UTF-8;page=21,the%20data:12345")!)) } func testShortensURLWhenShortVersionExpected() { - let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("http://some.domain.eu/with/path"), showsFullURL: false) + let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: false) XCTAssertEqual(addressForDisplay, "some.domain.eu") } func testDoesNotShortenURLWhenFullVersionExpected() { - let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("http://some.domain.eu/with/path"), showsFullURL: true) + let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "http://some.domain.eu/with/path")!, showsFullURL: true) XCTAssertEqual(addressForDisplay, "http://some.domain.eu/with/path") } func testFallsBackToLongURLWhenCannotProduceShortURL() { - let addressForDisplay = AddressHelper.addressForDisplay(url: #URL("file:///some/path"), showsFullURL: false) + let addressForDisplay = AddressHelper.addressForDisplay(url: URL(string: "file:///some/path")!, showsFullURL: false) XCTAssertEqual(addressForDisplay, "file:///some/path") } diff --git a/DuckDuckGoTests/AppURLsTests.swift b/DuckDuckGoTests/AppURLsTests.swift index c3221f0096..ff73d2f984 100644 --- a/DuckDuckGoTests/AppURLsTests.swift +++ b/DuckDuckGoTests/AppURLsTests.swift @@ -17,7 +17,6 @@ // limitations under the License. // -import Macros import XCTest @testable import BrowserServicesKit @@ -63,7 +62,7 @@ final class AppURLsTests: XCTestCase { } func testWhenRemoveInternalSearchParametersFromNonSearchUrlThenUrlIsUnchanged() { - let example = #URL("https://duckduckgo.com?atb=x&t=y&ko=z") + let example = URL(string: "https://duckduckgo.com?atb=x&t=y&ko=z")! let result = example.removingInternalSearchParameters() XCTAssertEqual(example.absoluteString, result.absoluteString) } @@ -79,13 +78,13 @@ final class AppURLsTests: XCTestCase { } func testBaseUrlDoesNotHaveSubDomain() { - XCTAssertEqual(URL.ddg, #URL("https://duckduckgo.com")) + XCTAssertEqual(URL.ddg, URL(string: "https://duckduckgo.com")!) } func testWhenMobileStatsParamsAreAppliedThenTheyReturnAnUpdatedUrl() throws { mockStatisticsStore.atb = "x" let actual = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .applyingStatsParams(to: #URL("http://duckduckgo.com?atb=wrong&t=wrong")) + .applyingStatsParams(to: URL(string: "http://duckduckgo.com?atb=wrong&t=wrong")!) XCTAssertEqual(actual.getParameter(named: "atb"), "x") XCTAssertEqual(actual.getParameter(named: "t"), "ddg_ios") } @@ -93,70 +92,70 @@ final class AppURLsTests: XCTestCase { func testWhenAtbMatchesThenHasMobileStatsParamsIsTrue() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_ios")) + .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_ios")!) XCTAssertTrue(result) } func testWhenAtbIsMismatchedThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "y" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_ios")) + .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_ios")!) XCTAssertFalse(result) } func testWhenAtbIsMissingThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?t=ddg_ios")) + .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?t=ddg_ios")!) XCTAssertFalse(result) } func testWhenSourceIsMismatchedThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=x&t=ddg_desktop")) + .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=x&t=ddg_desktop")!) XCTAssertFalse(result) } func testWhenSourceIsMissingThenHasMobileStatsParamsIsFalse() { mockStatisticsStore.atb = "x" let result = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) - .hasCorrectMobileStatsParams(url: #URL("http://duckduckgo.com?atb=y")) + .hasCorrectMobileStatsParams(url: URL(string: "http://duckduckgo.com?atb=y")!) XCTAssertFalse(result) } func testWhenUrlIsDdgWithASearchParamThenIsSearchIsTrue() { - let result = #URL("http://duckduckgo.com?q=hello").isDuckDuckGoSearch + let result = URL(string: "http://duckduckgo.com?q=hello")!.isDuckDuckGoSearch XCTAssertTrue(result) } func testWhenUrlHasNoSearchParamsThenIsSearchIsFalse() { - let result = #URL("http://duckduckgo.com?test=hello").isDuckDuckGoSearch + let result = URL(string: "http://duckduckgo.com?test=hello")!.isDuckDuckGoSearch XCTAssertFalse(result) } func testWhenUrlIsNonDdgThenIsSearchIsFalse() { - let result = #URL("http://www.example.com?q=hello").isDuckDuckGoSearch + let result = URL(string: "http://www.example.com?q=hello")!.isDuckDuckGoSearch XCTAssertFalse(result) } func testWhenNonDdgUrlHasDdgParamThenIsDdgIsFalse() { - let result = #URL("http://www.example.com?x=duckduckgo.com").isDuckDuckGo + let result = URL(string: "http://www.example.com?x=duckduckgo.com")!.isDuckDuckGo XCTAssertFalse(result) } func testWhenDdgUrlIsHttpThenIsDddgIsTrue() { - let result = #URL("http://duckduckgo.com").isDuckDuckGo + let result = URL(string: "http://duckduckgo.com")!.isDuckDuckGo XCTAssertTrue(result) } func testWhenDdgUrlIsHttpsThenIsDddgIsTrue() { - let result = #URL("https://duckduckgo.com").isDuckDuckGo + let result = URL(string: "https://duckduckgo.com")!.isDuckDuckGo XCTAssertTrue(result) } func testWhenDdgUrlHasSubdomainThenIsDddgIsTrue() { - let result = #URL("http://www.duckduckgo.com").isDuckDuckGo + let result = URL(string: "http://www.duckduckgo.com")!.isDuckDuckGo XCTAssertTrue(result) } @@ -236,7 +235,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryUsesVerticalThenItIsAppliedToNewOne() throws { - let contextURL = #URL("https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images") + let contextURL = URL(string: "https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images")! let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -245,7 +244,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryUsesVerticalWithMapsThenTheseAreIgnored() throws { - let contextURL = #URL("https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images&iaxm=maps") + let contextURL = URL(string: "https://duckduckgo.com/?q=query&iar=images&ko=-1&ia=images&iaxm=maps")! let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -256,7 +255,7 @@ final class AppURLsTests: XCTestCase { } func testWhenExistingQueryHasNoVerticalThenItIsAbsentInNewOne() throws { - let contextURL = #URL("https://example.com") + let contextURL = URL(string: "https://example.com")! let url = StatisticsDependentURLFactory(statisticsStore: mockStatisticsStore) .makeSearchURL(query: "query", queryContext: contextURL)! @@ -276,20 +275,20 @@ final class AppURLsTests: XCTestCase { } func testWhenDdgUrlWithSearchParamThenSearchQueryReturned() { - let url = #URL("https://www.duckduckgo.com/?ko=-1&kl=wt-wt&q=some%20search") + let url = URL(string: "https://www.duckduckgo.com/?ko=-1&kl=wt-wt&q=some%20search")! let expected = "some search" let actual = url.searchQuery XCTAssertEqual(actual, expected) } func testWhenNoSearchParamInDdgUrlThenSearchQueryReturnsNil() { - let url = #URL("https://www.duckduckgo.com/?ko=-1&kl=wt-wt") + let url = URL(string: "https://www.duckduckgo.com/?ko=-1&kl=wt-wt")! let result = url.searchQuery XCTAssertNil(result) } func testWhenNotDdgUrlThenSearchQueryReturnsNil() { - let url = #URL("https://www.test.com/?ko=-1&kl=wt-wt&q=some%20search") + let url = URL(string: "https://www.test.com/?ko=-1&kl=wt-wt&q=some%20search")! let result = url.searchQuery XCTAssertNil(result) } diff --git a/DuckDuckGoTests/BookmarksCachingSearchTests.swift b/DuckDuckGoTests/BookmarksCachingSearchTests.swift index 0efb242a11..03f79cb6d2 100644 --- a/DuckDuckGoTests/BookmarksCachingSearchTests.swift +++ b/DuckDuckGoTests/BookmarksCachingSearchTests.swift @@ -20,7 +20,6 @@ import Bookmarks import Combine import CoreData -import Macros import XCTest @testable import Core @@ -43,7 +42,7 @@ public class MockBookmarksSearchStore: BookmarksSearchStore { class BookmarksCachingSearchTests: XCTestCase { - let url = #URL("http://duckduckgo.com") + let url = URL(string: "http://duckduckgo.com")! let simpleStore = MockBookmarksSearchStore() let urlStore = MockBookmarksSearchStore() @@ -86,9 +85,9 @@ class BookmarksCachingSearchTests: XCTestCase { BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.f12a.rawValue, url: url, isFavorite: true)] urlStore.dataSet = [ - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample1.rawValue, url: #URL("https://example.com"), isFavorite: true), - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample2.rawValue, url: #URL("https://example.com"), isFavorite: true), - BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlNasa.rawValue, url: #URL("https://www.nasa.gov"), isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample1.rawValue, url: URL(string: "https://example.com")!, isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlExample2.rawValue, url: URL(string: "https://example.com")!, isFavorite: true), + BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlNasa.rawValue, url: URL(string: "https://www.nasa.gov")!, isFavorite: true), BookmarksCachingSearch.ScoredBookmark(objectID: mockObjectID, title: Entry.urlDDG.rawValue, url: url, isFavorite: true)] } @@ -239,6 +238,6 @@ class BookmarksCachingSearchTests: XCTestCase { private extension BookmarksCachingSearchTests { enum Constants { static let bookmarkTitle = "my bookmark" - static let bookmarkURL = #URL("https://www.apple.com") + static let bookmarkURL = URL(string: "https://www.apple.com")! } } diff --git a/DuckDuckGoTests/BookmarksImporterTests.swift b/DuckDuckGoTests/BookmarksImporterTests.swift index 94d2375de5..c82042cfab 100644 --- a/DuckDuckGoTests/BookmarksImporterTests.swift +++ b/DuckDuckGoTests/BookmarksImporterTests.swift @@ -18,7 +18,6 @@ // import Bookmarks -import Macros import SwiftSoup import XCTest @@ -197,6 +196,6 @@ private extension BookmarksImporterTests { enum Constants { static let bookmarkTitle = "my bookmark" static let bookmarkURLString = "https://duckduckgo.com" - static let bookmarkURL = #URL("https://duckduckgo.com") + static let bookmarkURL = URL(string: "https://duckduckgo.com")! } } diff --git a/DuckDuckGoTests/DaxDialogTests.swift b/DuckDuckGoTests/DaxDialogTests.swift index 1d0072f0b5..d987fba05c 100644 --- a/DuckDuckGoTests/DaxDialogTests.swift +++ b/DuckDuckGoTests/DaxDialogTests.swift @@ -19,7 +19,6 @@ import BrowserServicesKit import ContentBlocking -import Macros import PrivacyDashboard import TrackerRadarKit import XCTest @@ -48,13 +47,13 @@ final class DaxDialog: XCTestCase { struct URLs { - static let example = #URL("https://www.example.com") - static let ddg = #URL("https://duckduckgo.com?q=test") - static let facebook = #URL("https://www.facebook.com") - static let google = #URL("https://www.google.com") - static let ownedByFacebook = #URL("https://www.instagram.com") - static let amazon = #URL("https://www.amazon.com") - static let tracker = #URL("https://www.1dmp.io") + static let example = URL(string: "https://www.example.com")! + static let ddg = URL(string: "https://duckduckgo.com?q=test")! + static let facebook = URL(string: "https://www.facebook.com")! + static let google = URL(string: "https://www.google.com")! + static let ownedByFacebook = URL(string: "https://www.instagram.com")! + static let amazon = URL(string: "https://www.amazon.com")! + static let tracker = URL(string: "https://www.1dmp.io")! } diff --git a/DuckDuckGoTests/DownloadMocks.swift b/DuckDuckGoTests/DownloadMocks.swift index 4ff1c9bd4a..1601392430 100644 --- a/DuckDuckGoTests/DownloadMocks.swift +++ b/DuckDuckGoTests/DownloadMocks.swift @@ -18,7 +18,6 @@ // import Foundation -import Macros import WebKit @testable import DuckDuckGo @@ -56,7 +55,7 @@ class MockNavigationResponse: WKNavigationResponse { var mimeType: String? override var response: URLResponse { - let response = MockURLResponse(url: #URL("https://www.duck.com"), + let response = MockURLResponse(url: URL(string: "https://www.duck.com")!, mimeType: mimeType!, expectedContentLength: 1234, textEncodingName: "") diff --git a/DuckDuckGoTests/DownloadTestsHelper.swift b/DuckDuckGoTests/DownloadTestsHelper.swift index 073e463402..9020736aaf 100644 --- a/DuckDuckGoTests/DownloadTestsHelper.swift +++ b/DuckDuckGoTests/DownloadTestsHelper.swift @@ -18,12 +18,11 @@ // import Foundation -import Macros @testable import DuckDuckGo struct DownloadTestsHelper { - let mockURL = #URL("https://duck.com") + let mockURL = URL(string: "https://duck.com")! let tmpDirectory = FileManager.default.temporaryDirectory let downloadsDirectory: URL diff --git a/DuckDuckGoTests/FaviconRequestModifierTests.swift b/DuckDuckGoTests/FaviconRequestModifierTests.swift index b3431cd777..a13b3ec0bd 100644 --- a/DuckDuckGoTests/FaviconRequestModifierTests.swift +++ b/DuckDuckGoTests/FaviconRequestModifierTests.swift @@ -18,7 +18,6 @@ // import BrowserServicesKit -import Macros import XCTest @testable import Core @@ -76,7 +75,7 @@ class FaviconRequestModifierTests: XCTestCase { } func test() { - let request = URLRequest(url: #URL("https://www.example.com")) + let request = URLRequest(url: URL(string: "https://www.example.com")!) let result = FaviconRequestModifier(userAgentManager: userAgentManager).modified(for: request) XCTAssertTrue(result?.allHTTPHeaderFields?["User-Agent"]?.contains("DuckDuckGo") ?? false) } diff --git a/DuckDuckGoTests/FaviconsTests.swift b/DuckDuckGoTests/FaviconsTests.swift index 12d4bb494a..ddbb966caf 100644 --- a/DuckDuckGoTests/FaviconsTests.swift +++ b/DuckDuckGoTests/FaviconsTests.swift @@ -20,7 +20,6 @@ import Bookmarks import CoreData import Kingfisher -import Macros import XCTest @testable import Core @@ -99,8 +98,8 @@ class FaviconsTests: XCTestCase { switch options?[4] { case .alternativeSources(let sources): XCTAssertEqual(2, sources.count) - XCTAssertEqual(sources[0].url, #URL("https://example.com/favicon.ico")) - XCTAssertEqual(sources[1].url, #URL("http://example.com/favicon.ico")) + XCTAssertEqual(sources[0].url, URL(string: "https://example.com/favicon.ico")!) + XCTAssertEqual(sources[1].url, URL(string: "http://example.com/favicon.ico")!) default: XCTFail("Unexpected option") diff --git a/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift b/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift index 2dfacdc021..3a1925f62d 100644 --- a/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift +++ b/DuckDuckGoTests/FireproofFaviconUpdaterTests.swift @@ -20,7 +20,6 @@ import Bookmarks import Core import Foundation -import Macros import Persistence import XCTest @@ -87,7 +86,7 @@ class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { try createBookmark() image = UIImage() - let url = #URL("https://example.com/favicon.ico") + let url = URL(string: "https://example.com/favicon.ico")! let updater = FireproofFaviconUpdater(bookmarksDatabase: db, tab: self, favicons: self) updater.faviconUserScript(FaviconUserScript(), didRequestUpdateFaviconForHost: "example.com", withUrl: url) @@ -104,7 +103,7 @@ class FireproofFaviconUpdaterTests: XCTestCase, TabNotifying, FaviconProviding { try createBookmark() image = UIImage() - let url = #URL("https://example.com/favicon.ico") + let url = URL(string: "https://example.com/favicon.ico")! let updater = FireproofFaviconUpdater(bookmarksDatabase: db, tab: self, favicons: self) updater.faviconUserScript(FaviconUserScript(), didRequestUpdateFaviconForHost: "www.example.com", withUrl: url) diff --git a/DuckDuckGoTests/HTTPSUpgradeTests.swift b/DuckDuckGoTests/HTTPSUpgradeTests.swift index c32252598a..aed388f664 100644 --- a/DuckDuckGoTests/HTTPSUpgradeTests.swift +++ b/DuckDuckGoTests/HTTPSUpgradeTests.swift @@ -18,7 +18,6 @@ // import BrowserServicesKit -import Macros import OHHTTPStubs import OHHTTPStubsSwift import XCTest @@ -35,7 +34,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpsThenShouldUpgradeResultIsFalse() { let expect = expectation(description: "Https url should not be upgraded") - let url = #URL("https://upgradable.url") + let url = URL(string: "https://upgradable.url")! let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() @@ -50,7 +49,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsExcludedThenShouldUpgradeResultIsFalse() { let expect = expectation(description: "Excluded http:// urls should not be upgraded") - let url = #URL("http://excluded.url") + let url = URL(string: "http://excluded.url")! let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() @@ -64,7 +63,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndCanBeUpgradedThenShouldUpgradeIsTrue() { let expect = expectation(description: "Http url in list and should be upgraded") - let url = #URL("http://upgradable.url") + let url = URL(string: "http://upgradable.url")! let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter()), privacyConfig: WebKitTestHelper.preparePrivacyConfig( @@ -85,7 +84,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndHttpsUpgradesDisabledThenShouldUpgradeIsFalse() { let expect = expectation(description: "Http url in list and should not be upgraded") - let url = #URL("http://upgradable.url") + let url = URL(string: "http://upgradable.url")! let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter()), privacyConfig: WebKitTestHelper.preparePrivacyConfig( @@ -106,7 +105,7 @@ class HTTPSUpgradeTests: XCTestCase { func testWhenURLIsHttpAndCannotBeUpgradedThenShouldUpgradeIsFalse() { let expect = expectation(description: "Http url not in list should not be upgraded") - let url = #URL("http://unknown.url") + let url = URL(string: "http://unknown.url")! let testee = HTTPSUpgrade(store: MockHTTPSUpgradeStore(bloomFilter: bloomFilter())) testee.loadData() diff --git a/DuckDuckGoTests/MenuBookmarksViewModelTests.swift b/DuckDuckGoTests/MenuBookmarksViewModelTests.swift index e4f310fbb4..d7d3f90bf5 100644 --- a/DuckDuckGoTests/MenuBookmarksViewModelTests.swift +++ b/DuckDuckGoTests/MenuBookmarksViewModelTests.swift @@ -20,7 +20,6 @@ import Bookmarks import DuckDuckGo import Foundation -import Macros import Persistence import XCTest @@ -36,7 +35,7 @@ private extension MenuBookmarksViewModel { class MenuBookmarksViewModelTests: XCTestCase { - let url = #URL("https://test.com") + let url = URL(string: "https://test.com")! var db: CoreDataDatabase! override func setUpWithError() throws { diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index ca0aea4c51..94c1cb0def 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -20,7 +20,6 @@ import BrowserServicesKit import Foundation import GRDB -import Macros import SecureStorage // swiftlint:disable file_length @@ -224,7 +223,7 @@ final class MockSecureVault: AutofillSecureVault { // MARK: - Mock Providers private extension URL { - static let duckduckgo = #URL("https://duckduckgo.com/") + static let duckduckgo = URL(string: "https://duckduckgo.com/")! } class MockDatabaseProvider: AutofillDatabaseProvider { @@ -248,7 +247,7 @@ class MockDatabaseProvider: AutofillDatabaseProvider { static func recreateDatabase(withKey key: Data) throws -> Self { // swiftlint:disable:next force_cast - return try MockDatabaseProvider(file: #URL("https://duck.com"), key: Data()) as! Self + return try MockDatabaseProvider(file: URL(string: "https://duck.com")!, key: Data()) as! Self } func storeWebsiteCredentials(_ credentials: SecureVaultModels.WebsiteCredentials) throws -> Int64 { diff --git a/DuckDuckGoTests/MockUserAgent.swift b/DuckDuckGoTests/MockUserAgent.swift index 49c9897345..ce8147a4c8 100644 --- a/DuckDuckGoTests/MockUserAgent.swift +++ b/DuckDuckGoTests/MockUserAgent.swift @@ -19,7 +19,6 @@ import BrowserServicesKit import Foundation -import Macros import WebKit class MockUserAgentManager: UserAgentManager { @@ -34,7 +33,7 @@ class MockUserAgentManager: UserAgentManager { private func prepareUserAgent() { let webview = WKWebView() - webview.load(URLRequest.developerInitiated(#URL("about:blank"))) + webview.load(URLRequest.developerInitiated(URL(string: "about:blank")!)) getDefaultAgent(webView: webview) { [weak self] agent in // Reference webview instance to keep it in scope and allow UA to be returned diff --git a/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift b/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift index bb8f666020..661e14f46e 100644 --- a/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift +++ b/DuckDuckGoTests/NotFoundCachingDownloaderTests.swift @@ -17,7 +17,6 @@ // limitations under the License. // -import Macros import XCTest @testable import Core @@ -59,7 +58,7 @@ class NotFoundCachingDownloaderTests: XCTestCase { downloader.noFaviconsFound(forDomain: "example.com") let moreThanAWeekFromNow = Date().addingTimeInterval(60 * 60 * 24 * 8) - XCTAssertTrue(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"), referenceDate: moreThanAWeekFromNow)) + XCTAssertTrue(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!, referenceDate: moreThanAWeekFromNow)) guard let domains: [String: TimeInterval] = UserDefaults.app.object(forKey: UserDefaultsWrapper.Key.notFoundCache.rawValue) as? [String: TimeInterval] else { @@ -72,11 +71,11 @@ class NotFoundCachingDownloaderTests: XCTestCase { func testWhenMarkingDomainAsNotFoundThenShouldNotDownload() { downloader.noFaviconsFound(forDomain: "example.com") - XCTAssertFalse(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"))) + XCTAssertFalse(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!)) } func testWhenDomainNotMarkedAsNotFoundThenShouldNotDownload() { - XCTAssertTrue(downloader.shouldDownload(#URL("https://example.com/path/to/image.png"))) + XCTAssertTrue(downloader.shouldDownload(URL(string: "https://example.com/path/to/image.png")!)) } } diff --git a/DuckDuckGoTests/PrivacyIconLogicTests.swift b/DuckDuckGoTests/PrivacyIconLogicTests.swift index 7ff6ad326f..a3245ac9d0 100644 --- a/DuckDuckGoTests/PrivacyIconLogicTests.swift +++ b/DuckDuckGoTests/PrivacyIconLogicTests.swift @@ -19,7 +19,6 @@ import BrowserServicesKit import Foundation -import Macros import PrivacyDashboard import TrackerRadarKit import XCTest @@ -29,11 +28,11 @@ import XCTest class PrivacyIconLogicTests: XCTestCase { - static let pageURL = #URL("https://example.com") - static let insecurePageURL = #URL("http://example.com") - static let ddgSearchURL = #URL("https://duckduckgo.com/?q=catfood&t=h_&ia=web") - static let ddgMainURL = #URL("https://duckduckgo.com") - static let ddgSupportURL = #URL("https://duckduckgo.com/email/settings/support") + static let pageURL = URL(string: "https://example.com")! + static let insecurePageURL = URL(string: "http://example.com")! + static let ddgSearchURL = URL(string: "https://duckduckgo.com/?q=catfood&t=h_&ia=web")! + static let ddgMainURL = URL(string: "https://duckduckgo.com")! + static let ddgSupportURL = URL(string: "https://duckduckgo.com/email/settings/support")! func testPrivacyIconIsShieldForPageURL() { let url = PrivacyIconLogicTests.insecurePageURL diff --git a/DuckDuckGoTests/TabTests.swift b/DuckDuckGoTests/TabTests.swift index e8eaa73ba4..a266bb759a 100644 --- a/DuckDuckGoTests/TabTests.swift +++ b/DuckDuckGoTests/TabTests.swift @@ -17,7 +17,6 @@ // limitations under the License. // -import Macros import XCTest @testable import Core @@ -27,8 +26,8 @@ class TabTests: XCTestCase { struct Constants { static let title = "A title" - static let url = #URL("https://example.com") - static let differentUrl = #URL("https://aDifferentUrl.com") + static let url = URL(string: "https://example.com")! + static let differentUrl = URL(string: "https://aDifferentUrl.com")! } func testWhenDesktopPropertyChangesThenObserversNotified() { @@ -147,7 +146,7 @@ class TabTests: XCTestCase { } private func link() -> Link { - return Link(title: "title", url: #URL("http://example.com")) + return Link(title: "title", url: URL(string: "http://example.com")!) } } diff --git a/DuckDuckGoTests/TabsModelTests.swift b/DuckDuckGoTests/TabsModelTests.swift index 405e4922ea..286ac7abd0 100644 --- a/DuckDuckGoTests/TabsModelTests.swift +++ b/DuckDuckGoTests/TabsModelTests.swift @@ -17,7 +17,6 @@ // limitations under the License. // -import Macros import XCTest @testable import DuckDuckGo @@ -25,7 +24,7 @@ import XCTest class TabsModelTests: XCTestCase { - private let exampleLink = Link(title: nil, url: #URL("https://example.com")) + private let exampleLink = Link(title: nil, url: URL(string: "https://example.com")!) private var emptyModel: TabsModel { return TabsModel(desktop: false) @@ -40,9 +39,9 @@ class TabsModelTests: XCTestCase { private var filledModel: TabsModel { let model = TabsModel(tabs: [ - Tab(link: Link(title: "url1", url: #URL("https://ur1l.com"))), - Tab(link: Link(title: "url2", url: #URL("https://ur12.com"))), - Tab(link: Link(title: "url3", url: #URL("https://ur13.com"))) + Tab(link: Link(title: "url1", url: URL(string: "https://ur1l.com")!)), + Tab(link: Link(title: "url2", url: URL(string: "https://ur12.com")!)), + Tab(link: Link(title: "url3", url: URL(string: "https://ur13.com")!)) ], desktop: false) return model } @@ -104,7 +103,7 @@ class TabsModelTests: XCTestCase { } func testWhenTabExistsThenIndexReturned() { - let tab = Tab(link: Link(title: nil, url: #URL("https://www.example.com"))) + let tab = Tab(link: Link(title: nil, url: URL(string: "https://www.example.com")!)) let testee = filledModel testee.add(tab: tab) XCTAssertEqual(testee.indexOf(tab: tab), 3) diff --git a/DuckDuckGoTests/TrackerAnimationLogicTests.swift b/DuckDuckGoTests/TrackerAnimationLogicTests.swift index b3088d01d7..fe65cd5d5c 100644 --- a/DuckDuckGoTests/TrackerAnimationLogicTests.swift +++ b/DuckDuckGoTests/TrackerAnimationLogicTests.swift @@ -20,7 +20,6 @@ import BrowserServicesKit import ContentBlocking import Foundation -import Macros import PrivacyDashboard import TrackerRadarKit import XCTest @@ -30,7 +29,7 @@ import XCTest class TrackerAnimationLogicTests: XCTestCase { - static let pageURL = #URL("https://example.com") + static let pageURL = URL(string: "https://example.com")! func testAnimationLogicToAnimateTrackersIfAnyBlocked() { let trackerInfo = makeBlockedTrackerInfo(pageURL: Self.pageURL) diff --git a/DuckDuckGoTests/UserAgentTests.swift b/DuckDuckGoTests/UserAgentTests.swift index 22b6e6e509..e3745595aa 100644 --- a/DuckDuckGoTests/UserAgentTests.swift +++ b/DuckDuckGoTests/UserAgentTests.swift @@ -18,7 +18,6 @@ // import BrowserServicesKit -import Macros import WebKit import XCTest @@ -66,11 +65,11 @@ final class UserAgentTests: XCTestCase { } private struct Constants { - static let url = #URL("http://example.com/index.html") - static let noAppUrl = #URL("http://cvs.com/index.html") - static let noAppSubdomainUrl = #URL("http://subdomain.cvs.com/index.html") - static let ddgFixedUrl = #URL("http://test2.com/index.html") - static let ddgDefaultUrl = #URL("http://test3.com/index.html") + static let url = URL(string: "http://example.com/index.html")! + static let noAppUrl = URL(string: "http://cvs.com/index.html")! + static let noAppSubdomainUrl = URL(string: "http://subdomain.cvs.com/index.html")! + static let ddgFixedUrl = URL(string: "http://test2.com/index.html")! + static let ddgDefaultUrl = URL(string: "http://test3.com/index.html")! } let testConfig = """ diff --git a/LocalPackages/Waitlist/Package.swift b/LocalPackages/Waitlist/Package.swift index b89a136609..c8b231fae9 100644 --- a/LocalPackages/Waitlist/Package.swift +++ b/LocalPackages/Waitlist/Package.swift @@ -23,7 +23,6 @@ let package = Package( name: "Waitlist", dependencies: [ "DesignResourcesKit", - .product(name: "Macros", package: "apple-toolbox"), ], swiftSettings: [ .define("DEBUG", .when(configuration: .debug)) diff --git a/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift b/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift index 1058a46a3f..63dab2041f 100644 --- a/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift +++ b/LocalPackages/Waitlist/Sources/Waitlist/Network/ProductWaitlistRequest.swift @@ -18,7 +18,6 @@ // import Foundation -import Macros public typealias ProductWaitlistMakeHTTPRequest = (URL, _ method: String, _ body: Data?, @escaping ProductWaitlistHTTPRequestCompletion) -> Void public typealias ProductWaitlistHTTPRequestCompletion = (Data?, Error?) -> Void @@ -134,9 +133,9 @@ public class ProductWaitlistRequest: WaitlistRequest { private var endpoint: URL { #if DEBUG - return #URL("https://quack.duckduckgo.com/api/auth/waitlist/") + return URL(string: "https://quack.duckduckgo.com/api/auth/waitlist/")! #else - return #URL("https://quack.duckduckgo.com/api/auth/waitlist/") + return URL(string: "https://quack.duckduckgo.com/api/auth/waitlist/")! #endif } diff --git a/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift b/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift index 0dd29425fd..51236cdf43 100644 --- a/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift +++ b/LocalPackages/Waitlist/Sources/WaitlistMocks/TestWaitlist.swift @@ -18,7 +18,6 @@ // import Foundation -import Macros import Waitlist public struct TestWaitlist: Waitlist { @@ -39,7 +38,7 @@ public struct TestWaitlist: Waitlist { public static var identifier: String = "mockIdentifier" public static var apiProductName: String = "mockApiProductName" - public static var downloadURL: URL = #URL("https://duckduckgo.com") + public static var downloadURL: URL = URL(string: "https://duckduckgo.com")! public static var backgroundTaskName: String = "BG Task" public static var backgroundRefreshTaskIdentifier: String = "bgtask" diff --git a/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift b/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift index 3fc237560b..0fcb1d6cbd 100644 --- a/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift +++ b/LocalPackages/Waitlist/Tests/WaitlistTests/WaitlistViewModelTests.swift @@ -18,7 +18,6 @@ // import Combine -import Macros import UserNotifications import WaitlistMocks import XCTest @@ -263,7 +262,7 @@ extension WaitlistViewModel { waitlistRequest: waitlistRequest, waitlistStorage: waitlistStorage, notificationService: notificationService, - downloadURL: #URL("https://duckduckgo.com") + downloadURL: URL(string: "https://duckduckgo.com")! ) } } diff --git a/Widgets/WidgetViews.swift b/Widgets/WidgetViews.swift index 81f71f056e..7991d20620 100644 --- a/Widgets/WidgetViews.swift +++ b/Widgets/WidgetViews.swift @@ -17,7 +17,6 @@ // limitations under the License. // -import Macros import SwiftUI import WidgetKit @@ -307,7 +306,7 @@ extension Image { struct WidgetViews_Previews: PreviewProvider { static let mockFavorites: [Favorite] = { - let duckDuckGoFavorite = Favorite(url: #URL("https://duckduckgo.com/"), + let duckDuckGoFavorite = Favorite(url: URL(string: "https://duckduckgo.com/")!, domain: "duckduckgo.com", title: "title", favicon: nil) diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index a603ff9af2..6b7ad1e7f1 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit a603ff9af22ca3ff7ce2e7ffbfe18c447d9f23e8 +Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 From 1b95f601d658409130971330859a6b13b768e1ad Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 14 Mar 2024 10:42:15 +0100 Subject: [PATCH 117/245] Update BSK to include latest macOS changes (#2588) Task/Issue URL: https://app.asana.com/0/414235014887631/1206800069675133/f macOS: https://github.com/duckduckgo/macos-browser/pull/2392 BSK: https://github.com/duckduckgo/BrowserServicesKit/pull/721 ## Description Updates BSK, but the changes are for macOS only. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 292438d4d1..71d3010b2a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10020,7 +10020,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 122.2.0; + version = 122.2.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index dc42e87ea6..2490c6bcab 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" : "1295a22823157c9b7d11793299cd58b189e87629", + "version" : "122.2.1" } }, { From 075c55d49ad2f98b7f485d07e9e386e23a4c2593 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Thu, 14 Mar 2024 10:42:16 -0400 Subject: [PATCH 118/245] NetP x Subscription Clean-up (#2565) Task/Issue URL: https://app.asana.com/0/0/1206470585910126/f Tech Design URL: CC: Description: This PR updates the NetP token store so that it uses the subscription access token if subscription is enabled. --- Core/BundleExtensions.swift | 40 ------------------- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/MainViewController.swift | 15 ------- ...orkProtectionConvenienceInitialisers.swift | 14 ++++++- ...etworkProtectionPacketTunnelProvider.swift | 29 ++++++++++---- 6 files changed, 38 insertions(+), 66 deletions(-) delete mode 100644 Core/BundleExtensions.swift diff --git a/Core/BundleExtensions.swift b/Core/BundleExtensions.swift deleted file mode 100644 index 2ba420193f..0000000000 --- a/Core/BundleExtensions.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// BundleExtensions.swift -// DuckDuckGo -// -// 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 Bundle { - public func appGroup(bundle: BundleGroup) -> String { - var appGroupName: String - - switch bundle { - case .subs: - appGroupName = "SUBSCRIPTION_APP_GROUP" - } - - guard let appGroup = object(forInfoDictionaryKey: appGroupName) as? String else { - fatalError("Info.plist is missing \(appGroupName)") - } - return appGroup - } -} - -public enum BundleGroup { - case subs -} diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 71d3010b2a..c3e6ebdfb3 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10020,7 +10020,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 122.2.1; + version = 123.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2490c6bcab..40b32b6aa1 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" : "1295a22823157c9b7d11793299cd58b189e87629", - "version" : "122.2.1" + "revision" : "d110859c6100daa59bcbc200dd8565975faf2151", + "version" : "123.0.0" } }, { diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 704e2098ea..30a2b813ab 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1392,23 +1392,8 @@ class MainViewController: UIViewController { @objc private func onNetworkProtectionAccountSignIn(_ notification: Notification) { - guard let token = AccountManager().accessToken else { - assertionFailure("[NetP Subscription] AccountManager signed in but token could not be retrieved") - return - } - tunnelDefaults.resetEntitlementMessaging() print("[NetP Subscription] Reset expired entitlement messaging") - - Task { - do { - // todo - https://app.asana.com/0/0/1206541966681608/f - try NetworkProtectionKeychainTokenStore().store(NetworkProtectionKeychainTokenStore.makeToken(from: token)) - print("[NetP Subscription] Stored derived NetP auth token") - } catch { - print("[NetP Subscription] Failed to store derived NetP auth token: \(error)") - } - } } #endif diff --git a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift index 922220f2a8..d35977e04c 100644 --- a/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift +++ b/DuckDuckGo/NetworkProtectionConvenienceInitialisers.swift @@ -24,6 +24,10 @@ import UIKit import Common import NetworkExtension +#if SUBSCRIPTION +import Subscription +#endif + private class DefaultTunnelSessionProvider: TunnelSessionProvider { func activeSession() async -> NETunnelProviderSession? { try? await ConnectionSessionUtilities.activeSession() @@ -56,10 +60,18 @@ extension ConnectionServerInfoObserverThroughSession { extension NetworkProtectionKeychainTokenStore { convenience init() { + let isSubscriptionEnabled = AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription) +#if SUBSCRIPTION && ALPHA + let accessTokenProvider: () -> String? = { AccountManager().accessToken } +#else + let accessTokenProvider: () -> String? = { nil } +#endif + self.init(keychainType: .dataProtection(.unspecified), serviceName: "\(Bundle.main.bundleIdentifier!).authToken", errorEvents: .networkProtectionAppDebugEvents, - isSubscriptionEnabled: AppDependencyProvider.shared.featureFlagger.isFeatureOn(.subscription)) + isSubscriptionEnabled: isSubscriptionEnabled, + accessTokenProvider: accessTokenProvider) } } diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index f4a05f4059..132171dcad 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -26,7 +26,10 @@ import Core import Networking import NetworkExtension import NetworkProtection + +#if SUBSCRIPTION import Subscription +#endif // swiftlint:disable type_body_length @@ -231,14 +234,23 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } @objc init() { -#if ALPHA +#if SUBSCRIPTION && ALPHA let isSubscriptionEnabled = true + let tokenStore = NetworkProtectionKeychainTokenStore( + keychainType: .dataProtection(.unspecified), + errorEvents: nil, + isSubscriptionEnabled: isSubscriptionEnabled, + accessTokenProvider: { AccountManager().accessToken } + ) #else let isSubscriptionEnabled = false + let tokenStore = NetworkProtectionKeychainTokenStore( + keychainType: .dataProtection(.unspecified), + errorEvents: nil, + isSubscriptionEnabled: isSubscriptionEnabled, + accessTokenProvider: { nil } + ) #endif - let tokenStore = NetworkProtectionKeychainTokenStore(keychainType: .dataProtection(.unspecified), - errorEvents: nil, - isSubscriptionEnabled: isSubscriptionEnabled) let errorStore = NetworkProtectionTunnelErrorStore() let notificationsPresenter = NetworkProtectionUNNotificationPresenter() let settings = VPNSettings(defaults: .networkProtectionGroupDefaults) @@ -306,17 +318,20 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } private static func entitlementCheck() async -> Result { -#if ALPHA +#if SUBSCRIPTION && ALPHA SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging -#endif - let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).hasEntitlement(for: .networkProtection) + let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) + .hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) switch result { case .success(let hasEntitlement): return .success(hasEntitlement) case .failure(let error): return .failure(error) } +#else + return .success(true) +#endif } } From 94958ae7bdfd937e9fa7f2278a3fa780ee40b116 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Thu, 14 Mar 2024 16:19:37 +0000 Subject: [PATCH 119/245] New log for SKAd 4 integration (#2594) Task/Issue URL: https://app.asana.com/0/1205842948507349/1206011173132781/f New logs for SKAd testing --- DuckDuckGo/AppDelegate+SKAD4.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/AppDelegate+SKAD4.swift b/DuckDuckGo/AppDelegate+SKAD4.swift index 672fd18ced..9f65d0eac3 100644 --- a/DuckDuckGo/AppDelegate+SKAD4.swift +++ b/DuckDuckGo/AppDelegate+SKAD4.swift @@ -27,8 +27,10 @@ extension AppDelegate { if #available(iOS 16.1, *) { SKAdNetwork.updatePostbackConversionValue(conversionValue, coarseValue: .high, lockWindow: true, completionHandler: { error in - if let error = error { + if let error { os_log("SKAD 4 postback failed %@", type: .error, error.localizedDescription) + } else { + os_log("SKAD 4 postback succeeded", type: .debug) } }) } else { From af3f93804484d6c55ca1d4f741764fbb00672017 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Thu, 14 Mar 2024 19:58:40 +0000 Subject: [PATCH 120/245] bump bsk to fix retain cycle (#2595) Task/Issue URL: https://app.asana.com/0/414235014887631/1206840485929811/f Tech Design URL: CC: Description: Bump BSK to fix retain cycle Steps to test this PR: Launch app --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c3e6ebdfb3..e75d3801d2 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10020,7 +10020,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 123.0.0; + version = 123.0.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 40b32b6aa1..686e365fd7 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" : "d110859c6100daa59bcbc200dd8565975faf2151", - "version" : "123.0.0" + "revision" : "838cb53a8f7050d87ae6931b45ce126ece994359", + "version" : "123.0.1" } }, { From 8993b63fd646a6fc94bbef7aa70290d93c42a082 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 14 Mar 2024 22:44:59 +0100 Subject: [PATCH 121/245] 17. Subscription Review Fixes (#2547) Task/Issue URL: https://app.asana.com/0/1204099484721401/1206757416402738/f Description: Copy and layout updates in Subscription > Add device Properly update account email when changes in the Add Device page Prevent showing the "Subscription Activation in progress" error while fetching subscription info --- DuckDuckGo/SettingsSubscriptionView.swift | 19 +++++++++++-------- DuckDuckGo/SettingsViewModel.swift | 5 +++++ .../SubscriptionRestoreViewModel.swift | 10 ++++++++++ .../Views/SubscriptionRestoreView.swift | 1 + DuckDuckGo/UserText.swift | 4 ++-- DuckDuckGo/en.lproj/Localizable.strings | 4 ++-- 6 files changed, 31 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 846e20893a..f5c01a7bef 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -172,17 +172,20 @@ struct SettingsSubscriptionView: View { var body: some View { if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { + if viewModel.state.subscription.hasActiveSubscription { - - // Allow managing the subscription if we have some entitlements - if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { - subscriptionDetailsView + + if !viewModel.isLoadingSubscriptionState { - // If no entitlements it should mean the backend is still out of sync - } else { - noEntitlementsAvailableView + // Allow managing the subscription if we have some entitlements + if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { + subscriptionDetailsView + + // If no entitlements it should mean the backend is still out of sync + } else { + noEntitlementsAvailableView + } } - } else { purchaseSubscriptionView diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index f67e383194..c383a5e9c7 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -56,6 +56,7 @@ final class SettingsViewModel: ObservableObject { // Sheet Presentation & Navigation @Published var isRestoringSubscription: Bool = false @Published var shouldDisplayRestoreSubscriptionError: Bool = false + @Published var isLoadingSubscriptionState: Bool = false @Published var shouldShowNetP = false @Published var shouldShowDBP = false @Published var shouldShowITP = false @@ -351,6 +352,7 @@ extension SettingsViewModel { return } + isLoadingSubscriptionState = true // Fetch available subscriptions from the backend (or sign out) switch await SubscriptionService.getSubscription(accessToken: token) { @@ -372,8 +374,11 @@ extension SettingsViewModel { } } } + isLoadingSubscriptionState = false + default: // Account is active but there's not a valid subscription / entitlements + isLoadingSubscriptionState = false signOutUser() } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index cbeee2fb26..d1c2f4e47d 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -55,6 +55,7 @@ final class SubscriptionRestoreViewModel: ObservableObject { } func initializeView() { + Task { await updateAccountEmail() } Pixel.fire(pixel: .privacyProSettingsAddDevice) subscriptionEmail = accountManager.email if accountManager.isUserAuthenticated { @@ -119,6 +120,15 @@ final class SubscriptionRestoreViewModel: ObservableObject { } } + @MainActor + private func updateAccountEmail() async { + if let token = accountManager.authToken, + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: token) { + accountManager.storeAccount(token: token, email: accountDetails.email, externalID: accountDetails.externalID) + subscriptionEmail = accountDetails.email + } + } + } #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index d6b1d7cf6a..c256d193f9 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -239,6 +239,7 @@ struct SubscriptionRestoreView: View { } .padding(Constants.cellPadding) .background(Color(designSystemColor: .panel)) + .cornerRadius(Constants.cornerRadius) .overlay( RoundedRectangle(cornerRadius: Constants.cornerRadius) .stroke(Color(designSystemColor: .lines), lineWidth: Constants.borderWidth) diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index c7cf2b241d..231e6fe716 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1096,8 +1096,8 @@ But if you *do* want a peek under the hood, you can find more information about // Add to other devices (AppleID / Email) public static let subscriptionAddDeviceTitle = NSLocalizedString("subscription.add.device.title", value: "Add Device", comment: "Add to another device view title") - public static let subscriptionAddDeviceHeaderTitle = NSLocalizedString("subscription.add.device.header.title", value: "Use your subscription on all your devices", comment: "Add subscription to other device title ") - public static let subscriptionAddDeviceDescription = NSLocalizedString("subscription.add.device.description", value: "Access your Privacy Pro subscription on other devices via an email address.", comment: "Subscription Add device Info") + public static let subscriptionAddDeviceHeaderTitle = NSLocalizedString("subscription.add.device.header.title", value: "Use your subscription on other devices", comment: "Add subscription to other device title ") + public static let subscriptionAddDeviceDescription = NSLocalizedString("subscription.add.device.description", value: "Access your Privacy Pro subscription via an email address.", comment: "Subscription Add device Info") public static let subscriptionAvailableInApple = NSLocalizedString("subscription.available.apple", value: "Privacy Pro is available on any device signed in to the same Apple ID.", comment: "Subscription availability message on Apple devices") public static let subscriptionManageEmailResendInstructions = NSLocalizedString("subscription.add.device.resend.instructions", value: "Resend Instructions", comment: "Resend activation instructions button") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 400655f967..502d7847a4 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2005,10 +2005,10 @@ But if you *do* want a peek under the hood, you can find more information about "subscription.add.device.button" = "Add to Another Device"; /* Subscription Add device Info */ -"subscription.add.device.description" = "Access your Privacy Pro subscription on other devices via an email address."; +"subscription.add.device.description" = "Access your Privacy Pro subscription via an email address."; /* Add subscription to other device title */ -"subscription.add.device.header.title" = "Use your subscription on all your devices"; +"subscription.add.device.header.title" = "Use your subscription on other devices"; /* Resend activation instructions button */ "subscription.add.device.resend.instructions" = "Resend Instructions"; From 415b75c8f67ebdb132b76f3b4baa93e711d10b53 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 14 Mar 2024 22:55:12 +0100 Subject: [PATCH 122/245] Subscriptions - 21. Manage Billing options to third parties (#2574) Task/Issue URL: https://app.asana.com/0/0/1206397634707138/f Description: Allows the user to display third party billing information (Google/Stripe) --- DuckDuckGo.xcodeproj/project.pbxproj | 4 + .../AsyncHeadlessWebViewModel.swift | 4 +- .../google-play.imageset/Contents.json | 21 +++++ .../google-play.imageset/google-play.svg | 6 ++ .../ViewModel/SubscriptionFlowViewModel.swift | 4 +- .../SubscriptionSettingsViewModel.swift | 90 ++++++++++++++----- .../Views/SubscriptionExternalLinkView.swift | 2 + .../Views/SubscriptionFlowView.swift | 1 + .../Views/SubscriptionGoogleView.swift | 66 ++++++++++++++ .../Views/SubscriptionSettingsView.swift | 23 +++-- DuckDuckGo/UserText.swift | 4 + DuckDuckGo/en.lproj/Localizable.strings | 6 ++ 12 files changed, 199 insertions(+), 32 deletions(-) create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/Contents.json create mode 100644 DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/google-play.svg create mode 100644 DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index e75d3801d2..3c12c96c14 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -787,6 +787,7 @@ CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */; }; + D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */; }; D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA152B7CF77300A0FBB9 /* Subscription */; }; D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; @@ -2455,6 +2456,7 @@ CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationURLDebugViewController.swift; sourceTree = ""; }; + D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionGoogleView.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailView.swift; sourceTree = ""; }; D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailViewModel.swift; sourceTree = ""; }; @@ -4641,6 +4643,7 @@ D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */, D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */, + D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */, ); path = Views; sourceTree = ""; @@ -6891,6 +6894,7 @@ 1E908BF129827C480008C8F3 /* AutoconsentUserScript.swift in Sources */, 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */, 1E7A71192934EC6100B7EA19 /* OmniBarNotificationContainerView.swift in Sources */, + D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */, 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift index 688f0ed213..02e09c59c6 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/AsyncHeadlessWebViewModel.swift @@ -62,7 +62,9 @@ final class AsyncHeadlessWebViewViewModel: ObservableObject { initialScrollPositionSubject.send(newPosition) isFirstUpdate = false } else { - subsequentScrollPositionSubject.send(newPosition) + DispatchQueue.main.async { + self.subsequentScrollPositionSubject.send(newPosition) + } } } diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/Contents.json new file mode 100644 index 0000000000..f9dd463792 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "google-play.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/google-play.svg b/DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/google-play.svg new file mode 100644 index 0000000000..c02939096d --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.xcassets/google-play.imageset/google-play.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 295b22e45b..d0fadf911b 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -239,9 +239,7 @@ final class SubscriptionFlowViewModel: ObservableObject { .receive(on: DispatchQueue.main) .sink { [weak self] value in guard let strongSelf = self else { return } - - let shouldNavigateBack = value && (strongSelf.webViewModel.url?.lastPathComponent != URL.subscriptionBaseURL.lastPathComponent) - strongSelf.canNavigateBack = shouldNavigateBack + strongSelf.canNavigateBack = value } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 102a365523..73fe6f48a8 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -27,20 +27,22 @@ import Core @available(iOS 15.0, *) final class SubscriptionSettingsViewModel: ObservableObject { - enum Constants { - static let monthlyProductID = "ios.subscription.1month" - static let yearlyProductID = "ios.subscription.1year" - static let updateFrequency: Float = 10 - } - let accountManager: AccountManager private var subscriptionUpdateTimer: Timer? private var signOutObserver: Any? + private var subscriptionInfo: SubscriptionService.GetSubscriptionResponse? @Published var subscriptionDetails: String = "" @Published var subscriptionType: String = "" @Published var shouldDisplayRemovalNotice: Bool = false @Published var shouldDismissView: Bool = false + @Published var shouldDisplayGoogleView: Bool = false + + // Used to display stripe WebUI + @Published var stripeViewModel: SubscriptionExternalLinkViewModel? + @Published var shouldDisplayStripeView: Bool = false + private var externalAllowedDomains = ["stripe.com"] + init(accountManager: AccountManager = AccountManager()) { self.accountManager = accountManager @@ -62,14 +64,33 @@ final class SubscriptionSettingsViewModel: ObservableObject { let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) switch subscriptionResult { case .success(let subscription): - updateSubscriptionDetails(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId) - case .failure(let error): + subscriptionInfo = subscription + updateSubscriptionsStatusMessage(status: subscription.status, + date: subscription.expiresOrRenewsAt, + product: subscription.productId, + billingPeriod: subscription.billingPeriod) + case .failure: AccountManager().signOut() shouldDismissView = true } } } + func manageSubscription() { + switch subscriptionInfo?.platform { + case .apple: + Task { await manageAppleSubscription() } + case .google: + manageGoogleSubscription() + case .stripe: + Task { await manageStripeSubscription() } + default: + return + } + } + + // MARK: - + private func setupNotificationObservers() { signOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { [weak self] _ in DispatchQueue.main.async { @@ -88,12 +109,11 @@ final class SubscriptionSettingsViewModel: ObservableObject { } } } - - private func updateSubscriptionDetails(status: Subscription.Status, date: Date, product: String) { + private func updateSubscriptionsStatusMessage(status: Subscription.Status, date: Date, product: String, billingPeriod: Subscription.BillingPeriod) { let statusString = (status == .autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires self.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) - self.subscriptionType = product == Constants.monthlyProductID ? UserText.subscriptionMonthly : UserText.subscriptionAnnual + self.subscriptionType = billingPeriod == .monthly ? UserText.subscriptionMonthly : UserText.subscriptionAnnual } func removeSubscription() { @@ -103,22 +123,46 @@ final class SubscriptionSettingsViewModel: ObservableObject { presentationLocation: .withoutBottomBar) } - func manageSubscription() { - if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { - Task { - do { - try await AppStore.showManageSubscriptions(in: windowScene) - } catch { - openSubscriptionManagementURL() - } - } + @MainActor private func manageAppleSubscription() async { + let url = URL.manageSubscriptionsInAppStoreAppURL + if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { + do { + try await AppStore.showManageSubscriptions(in: windowScene) + } catch { + self.openURL(url) + } + } else { + self.openURL(url) + } + } + + private func manageGoogleSubscription() { + shouldDisplayGoogleView = true + } + + private func manageStripeSubscription() async { + guard let token = accountManager.accessToken, let externalID = accountManager.externalID else { return } + let serviceResponse = await SubscriptionService.getCustomerPortalURL(accessToken: token, externalID: externalID) + + // Get Stripe Customer Portal URL and update the model + if case .success(let response) = serviceResponse { + guard let url = URL(string: response.customerPortalUrl) else { return } + if let existingModel = stripeViewModel { + existingModel.url = url } else { - openSubscriptionManagementURL() + let model = SubscriptionExternalLinkViewModel(url: url, allowedDomains: externalAllowedDomains) + DispatchQueue.main.async { + self.stripeViewModel = model + } } } + DispatchQueue.main.async { + self.shouldDisplayStripeView = true + } + } - private func openSubscriptionManagementURL() { - let url = URL.manageSubscriptionsInAppStoreAppURL + @MainActor + private func openURL(_ url: URL) { if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url, options: [:], completionHandler: nil) } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift b/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift index badb1932bd..5edfcc0155 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift @@ -27,6 +27,7 @@ struct SubscriptionExternalLinkView: View { @Environment(\.dismiss) var dismiss @ObservedObject var viewModel: SubscriptionExternalLinkViewModel + @State var title: String? enum Constants { static let navButtonPadding: CGFloat = 20.0 @@ -47,6 +48,7 @@ struct SubscriptionExternalLinkView: View { } .navigationBarTitleDisplayMode(.inline) .navigationViewStyle(.stack) + .navigationTitle(title ?? "") .onAppear(perform: { setUpAppearances() diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index f611c5ead8..5df4f65ece 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -210,6 +210,7 @@ struct SubscriptionFlowView: View { message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { viewModel.transactionError = nil + viewModel.finalizeSubscriptionFlow() }, secondaryButton: .default(Text(UserText.subscriptionFoundRestore)) { viewModel.restoreAppstoreTransaction() diff --git a/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift b/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift new file mode 100644 index 0000000000..394ea90b58 --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionGoogleView.swift @@ -0,0 +1,66 @@ +// +// SubscriptionGoogleView.swift +// DuckDuckGo +// +// 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 SwiftUI +#if SUBSCRIPTION +@available(iOS 15.0, *) + +struct SubscriptionGoogleView: View { + + enum Constants { + static let padding: CGFloat = 20.0 + } + + @Environment(\.dismiss) var dismiss + + var body: some View { + ZStack { + Color(designSystemColor: .background) + .edgesIgnoringSafeArea(.all) + VStack(alignment: .center) { + Image("google-play").padding(.top, Constants.padding) + + Text(UserText.subscriptionManageBillingGoogleText) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + .multilineTextAlignment(.center) + .padding(Constants.padding) + Spacer() + } + } + .navigationBarTitle(UserText.subscriptionManageBillingGoogleTitle, displayMode: .inline) + .applyInsetGroupedListStyle() + } + +} +#endif + + +#if SUBSCRIPTION && DEBUG +@available(iOS 15.0, *) + +struct SubscriptionGoogleView_Previews: PreviewProvider { + static var previews: some View { + NavigationView { + SubscriptionGoogleView().navigationBarTitleDisplayMode(.inline) + } + } +} +#endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index 76abb5875a..5760a2014a 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -35,7 +35,7 @@ struct SubscriptionSettingsView: View { @StateObject var viewModel = SubscriptionSettingsViewModel() @StateObject var sceneEnvironment = SceneEnvironment() @State var isFirstOnAppear = true - + @ViewBuilder private var optionsView: some View { List { @@ -65,6 +65,12 @@ struct SubscriptionSettingsView: View { isButton: true) } + .sheet(isPresented: $viewModel.shouldDisplayStripeView) { + if let stripeViewModel = viewModel.stripeViewModel { + SubscriptionExternalLinkView(viewModel: stripeViewModel, title: UserText.subscriptionManagePlan) + } + } + Section(header: Text(UserText.subscriptionManageDevices)) { NavigationLink(destination: SubscriptionRestoreView()) { @@ -92,6 +98,10 @@ struct SubscriptionSettingsView: View { }) } } + + NavigationLink(destination: SubscriptionGoogleView(), isActive: $viewModel.shouldDisplayGoogleView) { + EmptyView() + } } .navigationTitle(UserText.settingsPProManageSubscription) .applyInsetGroupedListStyle() @@ -122,6 +132,13 @@ struct SubscriptionSettingsView: View { } } + @ViewBuilder + private var stripeView: some View { + if let stripeViewModel = viewModel.stripeViewModel { + SubscriptionExternalLinkView(viewModel: stripeViewModel) + } + } + var body: some View { Group { if #available(iOS 16.0, *) { @@ -151,10 +168,6 @@ struct SubscriptionSettingsView_Previews: PreviewProvider { NavigationView { SubscriptionSettingsView().navigationBarTitleDisplayMode(.inline) } - // You can customize the preview environment here if needed. - // For example, you can set a specific device, size, or dark mode/light mode. - // .previewDevice(PreviewDevice(rawValue: "iPhone 12")) - // .preferredColorScheme(.dark) } } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index 231e6fe716..fa1b65cba3 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1139,6 +1139,10 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionBackendErrorMessage = NSLocalizedString("subscription.restore.backend.error.message", value: "We’re having trouble connecting. Please try again later.", comment: "Alert for general error message") public static let subscriptionBackendErrorButton = NSLocalizedString("subscription.restore.backend.error.button", value: "Back to Settings", comment: "Button text for general error message") + public static let subscriptionManageBillingGoogleTitle = NSLocalizedString("subscription.billing.google.title", value: "Subscription Plans", comment: "Title for the manage billing page") + public static let subscriptionManageBillingGoogleText = NSLocalizedString("subscription.billing.google.text", value: "Your subscription was purchased through the Google Play Store. To renew your subscription, please open Google Play Store subscription settings on a device signed in to the same Google Account used to originally purchase your subscription.", comment: "Text for the manage billing page") + + // PIR: public static let subscriptionPIRHeroText = NSLocalizedString("subscription.pir.hero", value: "Activate Privacy Pro on desktop to set up Personal Information Removal", comment: "Hero Text for Personal information removal") public static let subscriptionPIRHeroDetail = NSLocalizedString("subscription.pir.heroText", value: "In the DuckDuckGo browser for desktop, go to %@ and click %@ to get started.", comment: "Description on how to use Personal information removal in desktop. The first placeholder references a location in the Desktop application. Privacy Pro>, and the second, the menu entry. i.e. ") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 502d7847a4..be87fc63fa 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -2037,6 +2037,12 @@ But if you *do* want a peek under the hood, you can find more information about /* Subscription availability message on Apple devices */ "subscription.available.apple" = "Privacy Pro is available on any device signed in to the same Apple ID."; +/* Text for the manage billing page */ +"subscription.billing.google.text" = "Your subscription was purchased through the Google Play Store. To renew your subscription, please open Google Play Store subscription settings on a device signed in to the same Google Account used to originally purchase your subscription."; + +/* Title for the manage billing page */ +"subscription.billing.google.title" = "Subscription Plans"; + /* Subscription Removal confirmation message */ "subscription.cancel.message" = "Your subscription has been removed from this device."; From 5e14018298d41c2a9e7aaad2be0b578dc2513159 Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Fri, 15 Mar 2024 13:45:50 +0100 Subject: [PATCH 123/245] Stub objects for Bookmarks DB (#2593) Task/Issue URL: https://app.asana.com/0/414235014887631/1206754257727808/f Description: Provide implementation for stub objects. Steps to test this PR: See BSK PR. --- Core/BookmarksCachingSearch.swift | 5 +- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/FireproofFaviconUpdater.swift | 2 + DuckDuckGo/RemoteMessaging.swift | 19 ++++---- DuckDuckGo/SyncDebugViewController.swift | 46 ++++++++++++++++++- 6 files changed, 63 insertions(+), 15 deletions(-) diff --git a/Core/BookmarksCachingSearch.swift b/Core/BookmarksCachingSearch.swift index abbaaff361..af6ea20826 100644 --- a/Core/BookmarksCachingSearch.swift +++ b/Core/BookmarksCachingSearch.swift @@ -63,9 +63,10 @@ public class CoreDataBookmarksSearchStore: BookmarksSearchStore { let fetchRequest = NSFetchRequest(entityName: "BookmarkEntity") fetchRequest.predicate = NSPredicate( - format: "%K = false AND %K == NO", + format: "%K = false AND %K == NO AND (%K == NO OR %K == nil)", #keyPath(BookmarkEntity.isFolder), - #keyPath(BookmarkEntity.isPendingDeletion) + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub) ) fetchRequest.resultType = .dictionaryResultType fetchRequest.propertiesToFetch = [#keyPath(BookmarkEntity.title), diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 3c12c96c14..4040f092aa 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10024,7 +10024,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 123.0.1; + version = 124.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 686e365fd7..ecf24dc606 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" : "838cb53a8f7050d87ae6931b45ce126ece994359", - "version" : "123.0.1" + "revision" : "b260e972faf56c8b8e26c12b8e9b9c5a6af4487d", + "version" : "124.0.0" } }, { diff --git a/DuckDuckGo/FireproofFaviconUpdater.swift b/DuckDuckGo/FireproofFaviconUpdater.swift index 5f46423b48..4e3ec9cafb 100644 --- a/DuckDuckGo/FireproofFaviconUpdater.swift +++ b/DuckDuckGo/FireproofFaviconUpdater.swift @@ -100,12 +100,14 @@ class FireproofFaviconUpdater: NSObject, FaviconUserScriptDelegate { let notFolderPredicate = NSPredicate(format: "%K = NO", #keyPath(BookmarkEntity.isFolder)) let notDeletedPredicate = NSPredicate(format: "%K = NO", #keyPath(BookmarkEntity.isPendingDeletion)) + let notStubsPredicate = NSPredicate(format: "%K == NO OR %K == nil", #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) let request = BookmarkEntity.fetchRequest() request.fetchLimit = 1 request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [ notFolderPredicate, notDeletedPredicate, + notStubsPredicate, domainPredicate ]) let result = (try? context.count(for: request)) ?? 0 > 0 diff --git a/DuckDuckGo/RemoteMessaging.swift b/DuckDuckGo/RemoteMessaging.swift index 6b64221d64..942181c690 100644 --- a/DuckDuckGo/RemoteMessaging.swift +++ b/DuckDuckGo/RemoteMessaging.swift @@ -114,19 +114,22 @@ struct RemoteMessaging { let displayedFavoritesFolder = BookmarkUtils.fetchFavoritesFolder(withUUID: favoritesDisplayMode.displayedFolder.rawValue, in: context)! let bookmarksCountRequest = BookmarkEntity.fetchRequest() - bookmarksCountRequest.predicate = NSPredicate(format: "SUBQUERY(%K, $x, $x CONTAINS %@).@count == 0 AND %K == false AND %K == false", - #keyPath(BookmarkEntity.favoriteFolders), - displayedFavoritesFolder, - #keyPath(BookmarkEntity.isFolder), - #keyPath(BookmarkEntity.isPendingDeletion)) + bookmarksCountRequest.predicate = NSPredicate( + format: "SUBQUERY(%K, $x, $x CONTAINS %@).@count == 0 AND %K == false AND %K == false AND (%K == NO OR %K == nil)", + #keyPath(BookmarkEntity.favoriteFolders), + displayedFavoritesFolder, + #keyPath(BookmarkEntity.isFolder), + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) bookmarksCount = (try? context.count(for: bookmarksCountRequest)) ?? 0 - + let favoritesCountRequest = BookmarkEntity.fetchRequest() - favoritesCountRequest.predicate = NSPredicate(format: "%K CONTAINS %@ AND %K == false AND %K == false", + favoritesCountRequest.predicate = NSPredicate(format: "%K CONTAINS %@ AND %K == false AND %K == false AND (%K == NO OR %K == nil)", #keyPath(BookmarkEntity.favoriteFolders), displayedFavoritesFolder, #keyPath(BookmarkEntity.isFolder), - #keyPath(BookmarkEntity.isPendingDeletion)) + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)) favoritesCount = (try? context.count(for: favoritesCountRequest)) ?? 0 } diff --git a/DuckDuckGo/SyncDebugViewController.swift b/DuckDuckGo/SyncDebugViewController.swift index eb214a800d..186e74d75d 100644 --- a/DuckDuckGo/SyncDebugViewController.swift +++ b/DuckDuckGo/SyncDebugViewController.swift @@ -54,6 +54,8 @@ class SyncDebugViewController: UITableViewController { enum ModelRows: Int, CaseIterable { case bookmarks + case bookmarksStubs + case bookmarksStubsCreate } @@ -97,7 +99,7 @@ class SyncDebugViewController: UITableViewController { return titles[section] } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) @@ -136,6 +138,21 @@ class SyncDebugViewController: UITableViewController { } else { cell.detailTextLabel?.text = "Error" } + case .bookmarksStubs: + cell.textLabel?.text = "Bookmark stubs" + + let context = bookmarksDatabase.makeContext(concurrencyType: .mainQueueConcurrencyType) + let fr = BookmarkEntity.fetchRequest() + fr.predicate = NSPredicate(format: "%K = TRUE", #keyPath(BookmarkEntity.isStub)) + + let result = try? context.count(for: fr) + if let result { + cell.detailTextLabel?.text = "\(result)" + } else { + cell.detailTextLabel?.text = "Error" + } + case .bookmarksStubsCreate: + cell.textLabel?.text = "Tap to create stubs" case .none: break @@ -167,7 +184,7 @@ class SyncDebugViewController: UITableViewController { } } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable:next cyclomatic_complexity function_body_length override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { switch Sections(rawValue: indexPath.section) { case .info: @@ -197,6 +214,31 @@ class SyncDebugViewController: UITableViewController { case .getRecoveryCode: showCopyPasteCodeAlert() + default: break + } + case .models: + switch ModelRows(rawValue: indexPath.row) { + case .bookmarksStubsCreate: + let context = bookmarksDatabase.makeContext(concurrencyType: .mainQueueConcurrencyType) + + let root = BookmarkUtils.fetchRootFolder(context)! + + _ = BookmarkEntity.makeBookmark(title: "Non stub", url: "url", parent: root, context: context) + let stub = BookmarkEntity.makeBookmark(title: "Stub", url: "", parent: root, context: context) + stub.isStub = true + let emptyStub = BookmarkEntity.makeBookmark(title: "", url: "", parent: root, context: context) + emptyStub.isStub = true + emptyStub.title = nil + emptyStub.url = nil + + do { + try context.save() + } catch { + assertionFailure("Could not create stubs") + } + + tableView.reloadData() + default: break } case .environment: From 152b08289b5af3626729490f89aa616bc49cdf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Fri, 15 Mar 2024 14:04:51 +0100 Subject: [PATCH 124/245] Roll back CPM post-rollout cleanup (#2599) --- Core/FeatureFlag.swift | 3 +++ DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/AppUserDefaults.swift | 26 +++++++++++++++++-- DuckDuckGo/Debug.storyboard | 13 ++++++++-- DuckDuckGo/RootDebugViewController.swift | 5 +++- DuckDuckGoTests/AppUserDefaultsTests.swift | 24 ++++++++++++++++- 7 files changed, 68 insertions(+), 9 deletions(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 0715f027d1..1246732321 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -36,6 +36,7 @@ public enum FeatureFlag: String { case networkProtectionWaitlistActive case subscription case swipeTabs + case autoconsentOnByDefault case history } @@ -66,6 +67,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault)) case .incontextSignup: return .remoteReleasable(.feature(.incontextSignup)) + case .autoconsentOnByDefault: + return .remoteReleasable(.subfeature(AutoconsentSubfeature.onByDefault)) case .history: return .remoteReleasable(.feature(.history)) } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4040f092aa..0e81bae808 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10024,7 +10024,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 124.0.0; + version = 124.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ecf24dc606..51185cd9fd 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" : "b260e972faf56c8b8e26c12b8e9b9c5a6af4487d", - "version" : "124.0.0" + "revision" : "bcafd206465427c560f9f581def57d8eef53748c", + "version" : "124.1.0" } }, { diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 7be4a03d4e..446bd75fc3 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -22,6 +22,7 @@ import Bookmarks import Core import WidgetKit +// swiftlint:disable file_length public class AppUserDefaults: AppSettings { public struct Notifications { @@ -290,8 +291,29 @@ public class AppUserDefaults: AppSettings { } } - @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: true) - var autoconsentEnabled: Bool + var autoconsentEnabled: Bool { + get { + // Use settings value if present + if let isEnabled = autoconsentEnabledSetting { + return isEnabled + } + + // Use onByDefault rollout otherwise + return featureFlagger.isFeatureOn(.autoconsentOnByDefault) + } + + set { + autoconsentEnabledSetting = newValue + } + } + + // Only for testing and `DebugViewController` purposes + func clearAutoconsentUserSetting() { + autoconsentEnabledSetting = nil + } + + @UserDefaultsWrapper(key: .autoconsentEnabled, defaultValue: false) + private var autoconsentEnabledSetting: Bool? var inspectableWebViewEnabled: Bool { get { diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 3fba4ea1a2..974a09e5a3 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -251,8 +251,17 @@ + + + + + + + + + - + @@ -261,7 +270,7 @@ - + diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 58d2cff77a..f7eda55ecc 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -31,6 +31,7 @@ import DDGSync class RootDebugViewController: UITableViewController { enum Row: Int { + case resetAutoconsentPrompt = 665 case crashFatalError = 666 case crashMemory = 667 case toggleInspectableWebViews = 668 @@ -117,10 +118,12 @@ class RootDebugViewController: UITableViewController { } if let rowTag = tableView.cellForRow(at: indexPath)?.tag, - let row = Row(rawValue: rowTag), + let row = Row(rawValue: rowTag), let cell = tableView.cellForRow(at: indexPath) { switch row { + case .resetAutoconsentPrompt: + AppUserDefaults().clearAutoconsentUserSetting() case .crashFatalError: fatalError(#function) case .crashMemory: diff --git a/DuckDuckGoTests/AppUserDefaultsTests.swift b/DuckDuckGoTests/AppUserDefaultsTests.swift index 373efa726f..8556b0643d 100644 --- a/DuckDuckGoTests/AppUserDefaultsTests.swift +++ b/DuckDuckGoTests/AppUserDefaultsTests.swift @@ -166,8 +166,30 @@ class AppUserDefaultsTests: XCTestCase { XCTAssertEqual(appUserDefaults.autofillCredentialsEnabled, false) } - func testDefaultAutoconsentStateIsTrue() { + func testDefaultAutoconsentStateIsFalse_WhenNotInRollout() { let appUserDefaults = AppUserDefaults(groupName: testGroupName) + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: false) + XCTAssertFalse(appUserDefaults.autoconsentEnabled) + } + + func testDefaultAutoconsentStateIsTrue_WhenInRollout() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: true) + XCTAssertTrue(appUserDefaults.autoconsentEnabled) + } + + func testAutoconsentReadsUserStoredValue_RegardlessOfRolloutState() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + + // When setting disabled by user and rollout enabled + appUserDefaults.autoconsentEnabled = false + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: true) + + XCTAssertFalse(appUserDefaults.autoconsentEnabled) + + // When setting enabled by user and rollout disabled + appUserDefaults.autoconsentEnabled = true + appUserDefaults.featureFlagger = createFeatureFlagger(withSubfeatureEnabled: false) XCTAssertTrue(appUserDefaults.autoconsentEnabled) } From ae006313ce7a1c1c6db445cc1780d0d99f07305d Mon Sep 17 00:00:00 2001 From: Maxim Tsoy Date: Fri, 15 Mar 2024 14:08:37 +0100 Subject: [PATCH 125/245] Bump autoconsent to 10.3.0 (#2601) --- DuckDuckGo/Autoconsent/autoconsent-bundle.js | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index 9463dd5805..68d3e39583 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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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 g(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 y="#truste-show-consent",w="#truste-consent-track",C=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${w}`],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(`${y},${w}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${w}`,"all")}openFrame(){this.click(y)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${w}`),this.click(y),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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 d{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 d{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")||(k(h(),"#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 d{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 b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],v=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var f=[{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:[]}],A={"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"}]}},E={autoconsent:f,consentomatic:A},x=Object.freeze({__proto__:null,autoconsent:f,consentomatic:A,default:E});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 v(this)}initialize(e,t){const o=g(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(){C.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,x);window.autoconsentMessageCallback=e=>{O.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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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 g(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 y="#truste-show-consent",w="#truste-consent-track",C=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${w}`],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(`${y},${w}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${w}`,"all")}openFrame(){this.click(y)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${w}`),this.click(y),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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 d{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 d{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")||(k(h(),"#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 d{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 b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],v=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var f=[{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:[]}],A={"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"}]}},E={autoconsent:f,consentomatic:A},x=Object.freeze({__proto__:null,autoconsent:f,consentomatic:A,default:E});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 v(this)}initialize(e,t){const o=g(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(){C.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,x);window.autoconsentMessageCallback=e=>{O.receiveMessageCallback(e)}}(); diff --git a/package-lock.json b/package-lock.json index 52bf0dfcf3..6688f65c51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "ios", "version": "1.0.0", "dependencies": { - "@duckduckgo/autoconsent": "^10.2.0" + "@duckduckgo/autoconsent": "^10.3.0" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", @@ -135,9 +135,9 @@ } }, "node_modules/@duckduckgo/autoconsent": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.2.0.tgz", - "integrity": "sha512-Q4sSGrvA5nWl5auJzttPQu1t25ff9N8Xj/UYglNKNqcnMAx/KxAIP5KbAFgf7JBru+q9Dq7muaEEB4FPU31fEw==" + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.3.0.tgz", + "integrity": "sha512-dUf37qkaYDuXEytU9mNNLGw28S1t1M1dFnvMHZDV9BpINVJeAl1ye7CmlABuGlDs6URrp2ZLZ5IxcKQhQglYcw==" }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", diff --git a/package.json b/package.json index 89d3c66b8c..30bc669090 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ "rollup-plugin-terser": "^7.0.2" }, "dependencies": { - "@duckduckgo/autoconsent": "^10.2.0" + "@duckduckgo/autoconsent": "^10.3.0" } } From cd0f8707e621637d6b810c204f6a8b9a9b6d18a9 Mon Sep 17 00:00:00 2001 From: Chris Brind Date: Fri, 15 Mar 2024 13:46:14 +0000 Subject: [PATCH 126/245] update embedded file --- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 64 ++++++++++++------- 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index beb758d321..8e97b6a832 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"73cfb8d6f397fd1de921f057db1bcc44\"" - public static let embeddedDataSHA = "3debb4a1e5c6cc292b3c03d9ea6ce4daa8073ab0c033131f2f8dbe1f752dfaf1" + public static let embeddedDataETag = "\"c595f46fe54bfa96bbff4f30fc3940d8\"" + public static let embeddedDataSHA = "911e6616b6869c0940c492240d43c0cf60274755dd45a50cc635c8b7c792cb87" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index 3da96e361b..d2593f8526 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1710155313369, + "version": 1710501855617, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -264,6 +264,18 @@ { "domain": "meneame.net" }, + { + "domain": "espn.com" + }, + { + "domain": "usaa.com" + }, + { + "domain": "publico.es" + }, + { + "domain": "cnbc.com" + }, { "domain": "earth.google.com" }, @@ -292,7 +304,7 @@ "state": "enabled", "features": { "onByDefault": { - "state": "enabled", + "state": "disabled", "rollout": { "steps": [ { @@ -308,7 +320,7 @@ } } }, - "hash": "7401ca90e7ef3907f1c0f8cdf163b994" + "hash": "9aaa080c235ddd8df4295c4d73c87a94" }, "autofill": { "exceptions": [ @@ -1028,8 +1040,11 @@ }, "clientBrandHint": { "exceptions": [], + "settings": { + "domains": [] + }, "state": "disabled", - "hash": "728493ef7a1488e4781656d3f9db84aa" + "hash": "d35dd75140cdfe166762013e59eb076d" }, "contentBlocking": { "state": "enabled", @@ -3925,6 +3940,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": [ @@ -4005,7 +4037,7 @@ ] }, "state": "enabled", - "hash": "065f70c0112e8f3a149bf4bc56f35203" + "hash": "2f1178300a22f85803bc42c676ea2cab" }, "exceptionHandler": { "exceptions": [ @@ -4670,11 +4702,6 @@ "state": "disabled", "hash": "d07b5bf740e4d648c94e1ac65c4305d9" }, - "notificationPermissions": { - "exceptions": [], - "state": "disabled", - "hash": "728493ef7a1488e4781656d3f9db84aa" - }, "privacyDashboard": { "exceptions": [], "features": { @@ -5929,22 +5956,11 @@ "fwmrm.net": { "rules": [ { - "rule": "2a7e9.v.fwmrm.net/ad/g/1", - "domains": [ - "channel4.com" - ] - }, - { - "rule": "2a7e9.v.fwmrm.net/ad/l/1", + "rule": "v.fwmrm.net/ad", "domains": [ + "6play.fr", "channel4.com" ] - }, - { - "rule": "7cbf2.v.fwmrm.net/ad/g/1", - "domains": [ - "6play.fr" - ] } ] }, @@ -7849,7 +7865,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "cff67ed6fc2962e0016ac3c65b6e4633" + "hash": "4d2da0fe5691d4283ebfb1021270c6ea" }, "trackingCookies1p": { "settings": { From 171e711bf8bb43853f29fd1acd11216057c68101 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 15 Mar 2024 14:00:57 +0000 Subject: [PATCH 127/245] Release 7.112.0-3 (#2602) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0e81bae808..1bf10ba1b0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8253,7 +8253,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8290,7 +8290,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8382,7 +8382,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8410,7 +8410,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8560,7 +8560,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8586,7 +8586,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8651,7 +8651,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8686,7 +8686,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8720,7 +8720,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8751,7 +8751,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9038,7 +9038,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9069,7 +9069,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9098,7 +9098,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9132,7 +9132,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9163,7 +9163,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9196,11 +9196,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9434,7 +9434,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9461,7 +9461,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9494,7 +9494,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9532,7 +9532,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9568,7 +9568,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9603,11 +9603,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9781,11 +9781,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9814,10 +9814,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From ffea516a9f452296e5dd602a78375b58f36b0b6f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Sat, 16 Mar 2024 03:18:24 +0100 Subject: [PATCH 128/245] 22. Subscription- Cleanup and minor updates (#2596) Task/Issue URL: https://app.asana.com/0/72649045549333/1206350282230222/f Description: Streamlines and cleans up implementation pre-release --- Core/Pixel.swift | 51 ++- DuckDuckGo.xcodeproj/project.pbxproj | 26 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/AppDelegate.swift | 2 + DuckDuckGo/MainViewController+Segues.swift | 4 +- DuckDuckGo/SettingsSubscriptionView.swift | 67 ++-- DuckDuckGo/SettingsView.swift | 94 +++++- DuckDuckGo/SettingsViewModel.swift | 153 ++++++--- DuckDuckGo/Subscription/Subscription.swift | 57 ++++ .../Contents.json | 0 .../Platform-Apple-16.svg | 0 .../Contents.json | 0 .../Platform-Windows-16.svg | 0 ...scriptionPagesUseSubscriptionFeature.swift | 93 +++-- .../SubscriptionEmailViewModel.swift | 134 ++++++-- .../ViewModel/SubscriptionFlowViewModel.swift | 234 ++++++------- .../SubscriptionRestoreViewModel.swift | 91 +++-- .../SubscriptionSettingsViewModel.swift | 77 +++-- .../Views/SubscriptionEmailView.swift | 109 ++++-- .../Views/SubscriptionFlowView.swift | 72 ++-- .../Views/SubscriptionITPView.swift | 2 +- .../Views/SubscriptionPIRView.swift | 4 +- .../Views/SubscriptionRestoreView.swift | 319 ++++++++++-------- .../Views/SubscriptionSettingsView.swift | 211 +++++++----- .../SubscriptionDebugViewController.swift | 2 +- DuckDuckGo/UserText.swift | 1 + DuckDuckGo/en.lproj/Localizable.strings | 3 + DuckDuckGoTests/PixelTests.swift | 32 ++ submodules/privacy-reference-tests | 2 +- 29 files changed, 1190 insertions(+), 654 deletions(-) create mode 100644 DuckDuckGo/Subscription/Subscription.swift rename DuckDuckGo/Subscription/Subscription.xcassets/{Platform-Apple-16.imageset => Platform-Apple-16-subscriptions.imageset}/Contents.json (100%) rename DuckDuckGo/Subscription/Subscription.xcassets/{Platform-Apple-16.imageset => Platform-Apple-16-subscriptions.imageset}/Platform-Apple-16.svg (100%) rename DuckDuckGo/Subscription/Subscription.xcassets/{Platform-Windows-16.imageset => Platform-Windows-16-subscriptions.imageset}/Contents.json (100%) rename DuckDuckGo/Subscription/Subscription.xcassets/{Platform-Windows-16.imageset => Platform-Windows-16-subscriptions.imageset}/Platform-Windows-16.svg (100%) diff --git a/Core/Pixel.swift b/Core/Pixel.swift index 0e30542c6b..06786bb9ee 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -150,7 +150,14 @@ public class Pixel { case atb case appVersion } + + + private enum Constant { + static let pixelStorageIdentifier = "com.duckduckgo.pixel.storage" + } + public static let storage = UserDefaults(suiteName: Constant.pixelStorageIdentifier)! + private init() { } @@ -160,16 +167,28 @@ public class Pixel { allowedQueryReservedCharacters: CharacterSet? = nil, withHeaders headers: APIRequest.Headers = APIRequest.Headers(), includedParameters: [QueryParameters] = [.atb, .appVersion], - onComplete: @escaping (Error?) -> Void = { _ in }) { - fire( - pixelNamed: pixel.name, - forDeviceType: deviceType, - withAdditionalParameters: params, - allowedQueryReservedCharacters: allowedQueryReservedCharacters, - withHeaders: headers, - includedParameters: includedParameters, - onComplete: onComplete - ) + onComplete: @escaping (Error?) -> Void = { _ in }, + debounce: Int = 0) { + + let date = Date().addingTimeInterval(-TimeInterval(debounce)) + if !pixel.hasBeenFiredSince(pixelStorage: storage, date: date) { + fire( + pixelNamed: pixel.name, + forDeviceType: deviceType, + withAdditionalParameters: params, + allowedQueryReservedCharacters: allowedQueryReservedCharacters, + withHeaders: headers, + includedParameters: includedParameters, + onComplete: onComplete + ) + updatePixelLastFireDate(pixel: pixel) + } else { + onComplete(nil) + } + } + + private static func updatePixelLastFireDate(pixel: Pixel.Event) { + storage.set(Date(), forKey: pixel.name) } public static func fire(pixelNamed pixelName: String, @@ -240,6 +259,18 @@ extension Pixel { } } +private extension Pixel.Event { + + func hasBeenFiredSince(pixelStorage: UserDefaults, date: Date) -> Bool { + if let lastFireDate = pixelStorage.object(forKey: name) as? Date { + return lastFireDate >= date + } + return false + } + + +} + /// NSError supports this through `NSUnderlyingError`, but there's no support for this for Swift's `Error`. This protocol does that. /// /// The reason why this protocol returns a code and a domain instead of just an `Error` or `NSError` is so that the error implementing diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1bf10ba1b0..f704909b8b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -787,6 +787,7 @@ CBDD5DE129A6741300832877 /* MockBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBDD5DE029A6741300832877 /* MockBundle.swift */; }; CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBEFB9102ADFFE7900DEDE7B /* CriticalAlerts.swift */; }; CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */; }; + D60170BD2BA34CE8001911B5 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60170BB2BA32DD6001911B5 /* Subscription.swift */; }; D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */; }; D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA152B7CF77300A0FBB9 /* Subscription */; }; D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */; }; @@ -2456,6 +2457,7 @@ CBF14FC427970AB0001D94D0 /* HomeMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageViewModel.swift; sourceTree = ""; }; CBF14FC627970C8A001D94D0 /* HomeMessageCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeMessageCollectionViewCell.swift; sourceTree = ""; }; CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationURLDebugViewController.swift; sourceTree = ""; }; + D60170BB2BA32DD6001911B5 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionGoogleView.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailView.swift; sourceTree = ""; }; @@ -4597,6 +4599,7 @@ D664C7922B289AA000CBFA76 /* Subscription */ = { isa = PBXGroup; children = ( + D60170BB2BA32DD6001911B5 /* Subscription.swift */, D6D95CE42B6DA3F200960317 /* AsyncHeadlessWebview */, D664C7952B289AA000CBFA76 /* Subscription.storekit */, D664C7932B289AA000CBFA76 /* ViewModel */, @@ -5752,7 +5755,7 @@ buildRules = ( ); dependencies = ( - B6F997CC2B8F380A00476735 /* PBXTargetDependency */, + D621BB1F2BA4E774007AC6A8 /* PBXTargetDependency */, F143C2EA1E4A4CD400CFDE3A /* PBXTargetDependency */, 8390447520BDCE10006461CD /* PBXTargetDependency */, 85482D932462DCD100EDEDD1 /* PBXTargetDependency */, @@ -6555,6 +6558,7 @@ B623C1C42862CD670043013E /* WKDownloadSession.swift in Sources */, EEFD562F2A65B6CA00DAEC48 /* NetworkProtectionInviteViewModel.swift in Sources */, 1E8AD1D927C4FEC100ABA377 /* DownloadsListSectioningHelper.swift in Sources */, + D60170BD2BA34CE8001911B5 /* Subscription.swift in Sources */, 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */, C12726F02A5FF89900215B02 /* EmailSignupPromptViewModel.swift in Sources */, D6E83C642B238432006C8AFB /* SettingsAboutView.swift in Sources */, @@ -7381,10 +7385,6 @@ target = 84E341911E2F7EFB00BDBA6F /* DuckDuckGo */; targetProxy = 9825F9CD293F2DE900F220F2 /* PBXContainerItemProxy */; }; - B6F997CC2B8F380A00476735 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = B6F997CB2B8F380A00476735 /* SwiftLintPlugin */; - }; B6F997CE2B8F380D00476735 /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = B6F997CD2B8F380D00476735 /* SwiftLintPlugin */; @@ -7425,6 +7425,10 @@ isa = PBXTargetDependency; productRef = B6F997DF2B8F383700476735 /* SwiftLintPlugin */; }; + D621BB1F2BA4E774007AC6A8 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + productRef = D621BB1E2BA4E774007AC6A8 /* SwiftLintPlugin */; + }; F143C2EA1E4A4CD400CFDE3A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = F143C2E31E4A4CD400CFDE3A /* Core */; @@ -10024,7 +10028,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 124.1.0; + version = 125.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10211,11 +10215,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; - B6F997CB2B8F380A00476735 /* SwiftLintPlugin */ = { - isa = XCSwiftPackageProductDependency; - package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; - productName = "plugin:SwiftLintPlugin"; - }; B6F997CD2B8F380D00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; @@ -10291,6 +10290,11 @@ package = 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */; productName = ZIPFoundation; }; + D621BB1E2BA4E774007AC6A8 /* SwiftLintPlugin */ = { + isa = XCSwiftPackageProductDependency; + package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; + productName = "plugin:SwiftLintPlugin"; + }; EE8E56892A56BCE400F11DCA /* NetworkProtection */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 51185cd9fd..137531c0e4 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" : "bcafd206465427c560f9f581def57d8eef53748c", - "version" : "124.1.0" + "revision" : "ac2127a26f75b2aa293f6036bcdd2bc241d09819", + "version" : "125.0.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index f6eaa9a624..ec4aedf063 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -376,7 +376,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private func presentExpiredEntitlementAlert() { let alertController = CriticalAlerts.makeExpiredEntitlementAlert { [weak self] in + #if SUBSCRIPTION self?.mainViewController?.segueToPrivacyPro() + #endif } window?.rootViewController?.present(alertController, animated: true) { [weak self] in self?.tunnelDefaults.showEntitlementAlert = false diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 0ea72c8bc3..8b6299af34 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -204,13 +204,15 @@ extension MainViewController { launchSettings() } +#if SUBSCRIPTION func segueToPrivacyPro() { os_log(#function, log: .generalLog, type: .debug) hideAllHighlightsIfNeeded() launchSettings { - $0.shouldNavigateToSubscriptionFlow = true + $0.triggerDeepLinkNavigation(to: .subscriptionFlow) } } +#endif func segueToDebugSettings() { os_log(#function, log: .generalLog, type: .debug) diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index f5c01a7bef..c0e19bf564 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -27,7 +27,9 @@ struct SettingsSubscriptionView: View { @EnvironmentObject var viewModel: SettingsViewModel @StateObject var subscriptionFlowViewModel = SubscriptionFlowViewModel() - @State var isShowingsubScriptionFlow = false + @StateObject var subscriptionRestoreViewModel = SubscriptionRestoreViewModel() + @State var isShowingSubscriptionFlow = false + @State var isShowingSubscriptionRestoreFlow = false @State var isShowingDBP = false @State var isShowingITP = false @@ -91,21 +93,29 @@ struct SettingsSubscriptionView: View { Group { SettingsCustomCell(content: { subscriptionDescriptionView }) SettingsCustomCell(content: { learnMoreView }, - action: { isShowingsubScriptionFlow = true }, + action: { isShowingSubscriptionFlow = true }, isButton: true ) - // Subscription Restore - .sheet(isPresented: $isShowingsubScriptionFlow, + // Subscription Purchase + .sheet(isPresented: $isShowingSubscriptionFlow, onDismiss: { Task { viewModel.onAppear() } }, - content: { SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() }) + content: { + SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() + }) SettingsCustomCell(content: { iHaveASubscriptionView }, action: { - isShowingsubScriptionFlow = true - subscriptionFlowViewModel.activateSubscriptionOnLoad = true + isShowingSubscriptionRestoreFlow = true }, isButton: true ) + // Subscription Restore + .sheet(isPresented: $isShowingSubscriptionRestoreFlow, + onDismiss: { Task { viewModel.onAppear() } }, + content: { + SubscriptionRestoreView(viewModel: subscriptionRestoreViewModel).interactiveDismissDisabled() + }) + } } @@ -193,33 +203,30 @@ struct SettingsSubscriptionView: View { } - .onChange(of: viewModel.shouldNavigateToDBP, perform: { value in - if value { - // Allow the sheet to dismiss before presenting a new one - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.navigationDelay) { - isShowingDBP = true - } - } - }) + // Selected Feature handler for Subscription Flow + .onChange(of: subscriptionFlowViewModel.selectedFeature) { value in + guard let value else { return } + viewModel.triggerDeepLinkNavigation(to: value) + } + + // Selected Feature handler for Subscription Restore + .onChange(of: subscriptionRestoreViewModel.emailViewModel.selectedFeature) { value in + guard let value else { return } + viewModel.triggerDeepLinkNavigation(to: value) + } - .onChange(of: viewModel.shouldNavigateToITP, perform: { value in + // Selected Feature handler for SubscriptionActivation + .onChange(of: subscriptionFlowViewModel.state.shouldActivateSubscription) { value in if value { - // Allow the sheet to dismiss before presenting a new one - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + Constants.navigationDelay) { - isShowingITP = true - } + viewModel.triggerDeepLinkNavigation(to: .subscriptionRestoreFlow) } - }) - - .onChange(of: viewModel.shouldNavigateToSubscriptionFlow, perform: { value in + } + + // Selected Feature handler for Show Plans + .onChange(of: subscriptionRestoreViewModel.state.shouldShowPlans) { value in if value { - isShowingsubScriptionFlow = true - } - }) - - .onReceive(subscriptionFlowViewModel.$selectedFeature) { value in - guard let value else { return } - viewModel.onAppearNavigationTarget = value + viewModel.triggerDeepLinkNavigation(to: .subscriptionFlow) + } } } } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index 0bfd697ef4..c15d879d28 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -26,7 +26,28 @@ struct SettingsView: View { @StateObject var viewModel: SettingsViewModel @Environment(\.presentationMode) var presentationMode + @State private var shouldDisplayDeepLinkSheet: Bool = false + @State private var shouldDisplayDeepLinkPush: Bool = false +#if SUBSCRIPTION + @State var deepLinkTarget: SettingsViewModel.SettingsDeepLinkSection? +#endif + var body: some View { + + // Hidden navigationLink for programatic navigation + if #available(iOS 15.0, *) { + + #if SUBSCRIPTION + if let target = deepLinkTarget { + NavigationLink(destination: deepLinkDestinationView(for: target), + isActive: $shouldDisplayDeepLinkPush) { + EmptyView() + } + } + #endif + } + + // Settings Sections List { SettingsGeneralView() SettingsSyncView() @@ -42,6 +63,7 @@ struct SettingsView: View { SettingsMoreView() SettingsAboutView() SettingsDebugView() + } .navigationBarTitle(UserText.settingsTitle, displayMode: .inline) .navigationBarItems(trailing: Button(UserText.navigationTitleDone) { @@ -50,11 +72,81 @@ struct SettingsView: View { .accentColor(Color(designSystemColor: .textPrimary)) .environmentObject(viewModel) .conditionalInsetGroupedListStyle() + .onAppear { viewModel.onAppear() } + + .onAppear { + viewModel.onDissapear() + } + +#if SUBSCRIPTION + // MARK: Deeplink Modifiers + + .sheet(isPresented: $shouldDisplayDeepLinkSheet, + onDismiss: { + viewModel.onAppear() + shouldDisplayDeepLinkSheet = false + }, + content: { + if #available(iOS 15.0, *) { + if let target = deepLinkTarget { + deepLinkDestinationView(for: target) + } + } + }) + + .onReceive(viewModel.$deepLinkTarget, perform: { link in + guard let link else { return } + self.deepLinkTarget = link + + switch link.type { + case .sheet: + DispatchQueue.main.async { + self.shouldDisplayDeepLinkSheet = true + } + case .navigation: + DispatchQueue.main.async { + self.shouldDisplayDeepLinkPush = true + } + case.UIKitView: + DispatchQueue.main.async { + triggerLegacyLink(link) + } + } + }) +#endif + } + +#if SUBSCRIPTION + // MARK: DeepLink Views + @available(iOS 15.0, *) + @ViewBuilder + func deepLinkDestinationView(for target: SettingsViewModel.SettingsDeepLinkSection) -> some View { + switch target { + case .dbp: + SubscriptionPIRView() + case .itr: + SubscriptionITPView() + case .subscriptionFlow: + SubscriptionFlowView() + case .subscriptionRestoreFlow: + SubscriptionRestoreView() + default: + EmptyView() + } } + private func triggerLegacyLink(_ link: SettingsViewModel.SettingsDeepLinkSection) { + switch link { + case .netP: + viewModel.presentLegacyView(.netP) + default: + return + } + } +#endif } struct InsetGroupedListStyleModifier: ViewModifier { @@ -66,7 +158,7 @@ struct InsetGroupedListStyleModifier: ViewModifier { } } } - + extension View { func conditionalInsetGroupedListStyle() -> some View { self.modifier(InsetGroupedListStyleModifier()) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index c383a5e9c7..75ffb5d304 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -79,13 +79,7 @@ final class SettingsViewModel: ObservableObject { var onRequestPopLegacyView: (() -> Void)? var onRequestDismissSettings: (() -> Void)? - // SwiftUI Programatic Navigation Variables - // Add more views as needed here... - @Published var shouldNavigateToDBP = false - @Published var shouldNavigateToITP = false - @Published var shouldNavigateToSubscriptionFlow = false - - // Our View State + // View State @Published private(set) var state: SettingsState // MARK: Cell Visibility @@ -102,15 +96,17 @@ final class SettingsViewModel: ObservableObject { } var shouldShowNoMicrophonePermissionAlert: Bool = false + +#if SUBSCRIPTION + // MARK: - Deep linking - // Used to automatically navigate on Appear to a specific section - enum SettingsSection: String { - case none, netP, dbp, itr, subscriptionFlow - } - - @Published var onAppearNavigationTarget: SettingsSection + // Used to automatically navigate to a specific section + // immediately after loading the Settings View + @Published private(set) var deepLinkTarget: SettingsDeepLinkSection? +#endif // MARK: Bindings + var themeBinding: Binding { Binding( get: { self.state.appTheme }, @@ -215,14 +211,15 @@ final class SettingsViewModel: ObservableObject { legacyViewProvider: SettingsLegacyViewProvider, accountManager: AccountManager, voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper, - navigateOnAppearDestination: SettingsSection = .none) { + deepLink: SettingsDeepLinkSection? = nil) { self.state = SettingsState.defaults self.legacyViewProvider = legacyViewProvider self.accountManager = accountManager self.voiceSearchHelper = voiceSearchHelper - self.onAppearNavigationTarget = navigateOnAppearDestination + self.deepLinkTarget = deepLink setupNotificationObservers() + } deinit { @@ -233,12 +230,10 @@ final class SettingsViewModel: ObservableObject { // MARK: Default Init init(state: SettingsState? = nil, legacyViewProvider: SettingsLegacyViewProvider, - voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper, - navigateOnAppearDestination: SettingsSection = .none) { + voiceSearchHelper: VoiceSearchHelperProtocol = AppDependencyProvider.shared.voiceSearchHelper) { self.state = SettingsState.defaults self.legacyViewProvider = legacyViewProvider self.voiceSearchHelper = voiceSearchHelper - self.onAppearNavigationTarget = navigateOnAppearDestination } #endif @@ -289,13 +284,13 @@ extension SettingsViewModel { #endif return SettingsState.NetworkProtection(enabled: enabled, status: "") } - + private func getSubscriptionState() async -> SettingsState.Subscription { - var enabled = false - var canPurchase = false - var hasActiveSubscription = false - -#if SUBSCRIPTION + var enabled = false + var canPurchase = false + var hasActiveSubscription = false + + #if SUBSCRIPTION if #available(iOS 15, *) { enabled = featureFlagger.isFeatureOn(.subscription) canPurchase = SubscriptionPurchaseEnvironment.canPurchase @@ -307,11 +302,11 @@ extension SettingsViewModel { } } } -#endif + #endif return SettingsState.Subscription(enabled: enabled, canPurchase: canPurchase, hasActiveSubscription: hasActiveSubscription) - } + } private func getSyncState() -> SettingsState.SyncSettings { SettingsState.SyncSettings(enabled: legacyViewProvider.syncService.featureFlags.contains(.userInterface), @@ -346,6 +341,7 @@ extension SettingsViewModel { @available(iOS 15.0, *) @MainActor private func setupSubscriptionEnvironment() async { + // Active subscription check guard let token = accountManager.accessToken else { setupSubscriptionPurchaseOptions() @@ -359,16 +355,16 @@ extension SettingsViewModel { case .success(let subscription) where subscription.isActive: // Check entitlements and update UI accordingly - let entitlements: [Entitlement.ProductName] = [.identityTheftRestoration, .dataBrokerProtection, .networkProtection] + let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case .success = await AccountManager().hasEntitlement(for: entitlement) { + if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { switch entitlement { case .identityTheftRestoration: - self.shouldShowITP = true + self.shouldShowITP = result case .dataBrokerProtection: - self.shouldShowDBP = true + self.shouldShowDBP = result case .networkProtection: - self.shouldShowNetP = true + self.shouldShowNetP = result case .unknown: return } @@ -467,8 +463,18 @@ extension SettingsViewModel { extension SettingsViewModel { func onAppear() { - Task { await initState() } - Task { await MainActor.run { navigateOnAppear() } } + Task { + await initState() +#if SUBSCRIPTION + triggerDeepLinkNavigation(to: self.deepLinkTarget) +#endif + } + } + + func onDissapear() { +#if SUBSCRIPTION + self.deepLinkTarget = nil +#endif } func setAsDefaultBrowser() { @@ -496,27 +502,6 @@ extension SettingsViewModel { onRequestDismissSettings?() } - @MainActor - private func navigateOnAppear() { - // We need a short delay to let the SwifttUI view lifecycle complete - // Otherwise the transition can be inconsistent - DispatchQueue.main.asyncAfter(deadline: .now() + 0.7) { - switch self.onAppearNavigationTarget { - case .netP: - self.presentLegacyView(.netP) - case .dbp: - self.shouldNavigateToDBP = true - case .itr: - self.shouldNavigateToITP = true - case .subscriptionFlow: - self.shouldNavigateToSubscriptionFlow = true - default: - break - } - self.onAppearNavigationTarget = .none - } - } - } // MARK: Legacy View Presentation @@ -595,4 +580,64 @@ extension SettingsViewModel: AutofillLoginSettingsListViewControllerDelegate { onRequestPopLegacyView?() } } + +// MARK: DeepLinks +#if SUBSCRIPTION +extension SettingsViewModel { + + enum SettingsDeepLinkSection: Identifiable { + case netP + case dbp + case itr + case subscriptionFlow + case subscriptionRestoreFlow + // Add other cases as needed + + var id: String { + switch self { + case .netP: return "netP" + case .dbp: return "dbp" + case .itr: return "itr" + case .subscriptionFlow: return "subscriptionFlow" + case .subscriptionRestoreFlow: return "subscriptionRestoreFlow" + // Ensure all cases are covered + } + } + + // Define the presentation type: .sheet or .push + // Default to .sheet, specify .push where needed + var type: DeepLinkType { + switch self { + // Specify cases that require .push presentation + // Example: + // case .dbp: + // return .push + case .netP: + return .UIKitView + default: + return .sheet + } + } + } + + // Define DeepLinkType outside the enum if not already defined + enum DeepLinkType { + case sheet + case navigation + case UIKitView + } + + // Navigate to a section in settings + func triggerDeepLinkNavigation(to target: SettingsDeepLinkSection?) { + guard let target else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.deepLinkTarget = target + + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + self.deepLinkTarget = nil + } + } + } +} +#endif // swiftlint:enable file_length diff --git a/DuckDuckGo/Subscription/Subscription.swift b/DuckDuckGo/Subscription/Subscription.swift new file mode 100644 index 0000000000..2ccb227e74 --- /dev/null +++ b/DuckDuckGo/Subscription/Subscription.swift @@ -0,0 +1,57 @@ +// +// Subscription.swift +// DuckDuckGo +// +// 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 + +enum SubscriptionPurchaseError: Error { + case purchaseFailed, + missingEntitlements, + failedToGetSubscriptionOptions, + failedToSetSubscription, + failedToRestorePastPurchase, + subscriptionExpired, + hasActiveSubscription, + cancelledByUser, + generalError +} + +enum SubscriptionFeatureName { + static let netP = "vpn" + static let itr = "identity-theft-restoration" + static let dbp = "personal-information-removal" + } + +enum SubscriptionFeatureSelection: Codable { + case netP + case itr + case dbp + + init?(featureName: String) { + switch featureName { + case SubscriptionFeatureName.netP: + self = .netP + case SubscriptionFeatureName.itr: + self = .itr + case SubscriptionFeatureName.dbp: + self = .dbp + default: + return nil + } + } +} diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16-subscriptions.imageset/Contents.json similarity index 100% rename from DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Contents.json rename to DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16-subscriptions.imageset/Contents.json diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Platform-Apple-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16-subscriptions.imageset/Platform-Apple-16.svg similarity index 100% rename from DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16.imageset/Platform-Apple-16.svg rename to DuckDuckGo/Subscription/Subscription.xcassets/Platform-Apple-16-subscriptions.imageset/Platform-Apple-16.svg diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16-subscriptions.imageset/Contents.json similarity index 100% rename from DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Contents.json rename to DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16-subscriptions.imageset/Contents.json diff --git a/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg b/DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16-subscriptions.imageset/Platform-Windows-16.svg similarity index 100% rename from DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16.imageset/Platform-Windows-16.svg rename to DuckDuckGo/Subscription/Subscription.xcassets/Platform-Windows-16-subscriptions.imageset/Platform-Windows-16.svg diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index f2d5a01c27..1aa70dac6f 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -66,10 +66,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec static let month = "monthly" static let year = "yearly" } - - struct FeatureSelection: Codable { - let feature: String - } enum UseSubscriptionError: Error { case purchaseFailed, @@ -87,13 +83,19 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec generalError } - // Transaction Status and erros are observed from ViewModels to handle errors in the UI + // Transaction Status and errors are observed from ViewModels to handle errors in the UI @Published private(set) var transactionStatus: SubscriptionTransactionStatus = .idle @Published private(set) var transactionError: UseSubscriptionError? - @Published private(set) var activateSubscription: Bool = false - @Published var selectedFeature: FeatureSelection? - @Published var emailActivationComplete: Bool = false + // Subscription Activation Actions + var onSetSubscription: (() -> Void)? + var onBackToSettings: (() -> Void)? + var onFeatureSelected: ((SubscriptionFeatureSelection) -> Void)? + var onActivateSubscription: (() -> Void)? + + struct FeatureSelection: Codable { + let feature: String + } weak var broker: UserScriptMessageBroker? @@ -113,17 +115,14 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { os_log("WebView handler: %s", log: .subscription, type: .debug, methodName) - switch methodName { case Handlers.getSubscription: return getSubscription case Handlers.setSubscription: return setSubscription - case Handlers.backToSettings: return backToSettings case Handlers.getSubscriptionOptions: return getSubscriptionOptions case Handlers.subscriptionSelected: return subscriptionSelected - case Handlers.activateSubscription: - Pixel.fire(pixel: .privacyProRestorePurchaseOfferPageEntry) - return activateSubscription + case Handlers.activateSubscription: return activateSubscription case Handlers.featureSelected: return featureSelected + case Handlers.backToSettings: return backToSettings default: return nil } @@ -268,6 +267,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { accountManager.storeAuthToken(token: authToken) accountManager.storeAccount(token: accessToken, email: accountDetails.email, externalID: accountDetails.externalID) + onSetSubscription?() } else { os_log("Failed to obtain subscription options", log: .subscription, type: .error) setTransactionError(.failedToSetSubscription) @@ -276,52 +276,50 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec return nil } - func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { - let accountManager = AccountManager() - if let accessToken = accountManager.accessToken, - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { - switch await SubscriptionService.getSubscription(accessToken: accessToken) { - - // If the account is not active, display an error and logout - case .success(let subscription) where !subscription.isActive: - setTransactionError(.failedToRestoreFromEmailSubscriptionInactive) - accountManager.signOut() - return nil - - case .success: - - // Store the account data and mark as active - accountManager.storeAccount(token: accessToken, - email: accountDetails.email, - externalID: accountDetails.externalID) - emailActivationComplete = true - - case .failure: - os_log("Failed to restore subscription from Email", log: .subscription, type: .error) - setTransactionError(.failedToRestoreFromEmail) - } - } else { - os_log("General error. Could not get account Details", log: .subscription, type: .error) - setTransactionError(.generalError) - } - return nil - } - func activateSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { - activateSubscription = true + Pixel.fire(pixel: .privacyProRestorePurchaseOfferPageEntry, debounce: 2) + onActivateSubscription?() return nil } func featureSelected(params: Any, original: WKScriptMessage) async -> Encodable? { guard let featureSelection: FeatureSelection = DecodableHelper.decode(from: params) else { assertionFailure("SubscriptionPagesUserScript: expected JSON representation of FeatureSelection") + return nil + } + + guard let featureSelection = SubscriptionFeatureSelection(featureName: featureSelection.feature) else { + assertionFailure("SubscriptionPagesUserScript: unexpected feature name value") setTransactionError(.generalError) return nil } - selectedFeature = featureSelection + + onFeatureSelected?(featureSelection) return nil } + + func backToSettings(params: Any, original: WKScriptMessage) async -> Encodable? { + let accountManager = AccountManager() + if let accessToken = accountManager.accessToken, + case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: accessToken) { + switch await SubscriptionService.getSubscription(accessToken: accessToken) { + + case .success: + accountManager.storeAccount(token: accessToken, + email: accountDetails.email, + externalID: accountDetails.externalID) + onBackToSettings?() + default: + break + } + + } else { + os_log("General error. Could not get account Details", log: .subscription, type: .error) + setTransactionError(.generalError) + } + return nil + } // MARK: Push actions (Push Data back to WebViews) enum SubscribeActionName: String { @@ -369,9 +367,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec func cleanup() { setTransactionStatus(.idle) setTransactionError(nil) - activateSubscription = false - emailActivationComplete = false - selectedFeature = nil broker = nil } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 8a5874fff9..638c1d313d 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -31,22 +31,31 @@ final class SubscriptionEmailViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature + private var canGoBackCancellable: AnyCancellable? + var emailURL = URL.activateSubscriptionViaEmail - var viewTitle = UserText.subscriptionActivateEmail - @Published var subscriptionEmail: String? - @Published var shouldReloadWebView = false - @Published var activateSubscription = false - @Published var managingSubscriptionEmail = false - @Published var transactionError: SubscriptionRestoreError? - @Published var navigationError: Bool = false - @Published var shouldDisplayInactiveError: Bool = false + var viewTitle = UserText.subscriptionActivateEmailTitle var webViewModel: AsyncHeadlessWebViewViewModel - private static let allowedDomains = [ - "duckduckgo.com", - "microsoftonline.com", - "duosecurity.com", - ] + struct State { + var subscriptionEmail: String? + var managingSubscriptionEmail = false + var transactionError: SubscriptionRestoreError? + var shouldDisplaynavigationError: Bool = false + var shouldDisplayInactiveError: Bool = false + var canNavigateBack: Bool = false + var shouldDismissView: Bool = false + var shouldDismissStack: Bool = false + var subscriptionActive: Bool = false + } + + // Read only View State - Should only be modified from the VM + @Published private(set) var state = State() + + // Publish the currently selected feature + @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? + + private static let allowedDomains = [ "duckduckgo.com" ] enum SubscriptionRestoreError: Error { case failedToRestoreFromEmail, @@ -67,10 +76,33 @@ final class SubscriptionEmailViewModel: ObservableObject { settings: AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, contentBlocking: false)) - initializeView() - setupTransactionObservers() + + Task { + await initializeView() + await setupSubscribers() + } + setupObservers() + } + + @MainActor + func navigateBack() async { + if state.canNavigateBack { + await webViewModel.navigationCoordinator.goBack() + } else { + state.shouldDismissView = true + } + } + + func resetDismissalState() { + state.shouldDismissView = false } + func onAppear() { + Task { await initializeView() } + webViewModel.navigationCoordinator.navigateTo(url: emailURL ) + } + + @MainActor private func initializeView() { if accountManager.isUserAuthenticated { // If user is authenticated, we want to "Add or manage email" instead of activating @@ -78,20 +110,52 @@ final class SubscriptionEmailViewModel: ObservableObject { viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle // Also we assume subscription requires managing, and not activation - managingSubscriptionEmail = true + state.managingSubscriptionEmail = true } } - private func setupTransactionObservers() { - subFeature.$emailActivationComplete + private func setupSubscribers() async { + canGoBackCancellable = webViewModel.$canGoBack .receive(on: DispatchQueue.main) .sink { [weak self] value in - if value { - self?.completeActivation() + self?.state.canNavigateBack = false + if self?.webViewModel.url != URL.activateSubscriptionViaEmail.forComparison() { + self?.state.canNavigateBack = value } } - .store(in: &cancellables) + } + + private func setupObservers() { + // Feature Callback + subFeature.onSetSubscription = { + UniquePixel.fire(pixel: .privacyProSubscriptionActivated) + DispatchQueue.main.async { + self.state.subscriptionActive = true + } + } + subFeature.onBackToSettings = { + self.dismissView() + } + + subFeature.onFeatureSelected = { feature in + DispatchQueue.main.async { + switch feature { + case .netP: + UniquePixel.fire(pixel: .privacyProWelcomeVPN) + self.selectedFeature = .netP + case .itr: + UniquePixel.fire(pixel: .privacyProWelcomePersonalInformationRemoval) + self.selectedFeature = .itr + case .dbp: + UniquePixel.fire(pixel: .privacyProWelcomeIdentityRestoration) + self.selectedFeature = .dbp + } + self.state.shouldDismissStack = true + } + + } + subFeature.$transactionError .receive(on: DispatchQueue.main) .removeDuplicates() @@ -108,32 +172,44 @@ final class SubscriptionEmailViewModel: ObservableObject { .sink { [weak self] error in guard let strongSelf = self else { return } DispatchQueue.main.async { - strongSelf.navigationError = error != nil ? true : false + strongSelf.state.shouldDisplaynavigationError = error != nil ? true : false } } .store(in: &cancellables) } + func shouldDisplayBackButton() -> Bool { + // Hide the back button after activation + if state.subscriptionActive && + (webViewModel.url == URL.subscriptionActivateSuccess.forComparison() || + webViewModel.url == URL.subscriptionPurchase.forComparison()) { + return false + } + return true + } + + // MARK: - + private func handleTransactionError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { switch error { case .subscriptionExpired: - transactionError = .subscriptionExpired + state.transactionError = .subscriptionExpired default: - transactionError = .generalError + state.transactionError = .generalError } - shouldDisplayInactiveError = true + state.shouldDisplayInactiveError = true } private func completeActivation() { DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailSuccess) - subFeature.emailActivationComplete = false - activateSubscription = true } - func loadURL() { - webViewModel.navigationCoordinator.navigateTo(url: emailURL ) + func dismissView() { + DispatchQueue.main.async { + self.state.shouldDismissView = true + } } deinit { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index d0fadf911b..caac67b827 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -31,53 +31,37 @@ final class SubscriptionFlowViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript let subFeature: SubscriptionPagesUseSubscriptionFeature let purchaseManager: PurchaseManager - let viewTitle = UserText.settingsPProSection var webViewModel: AsyncHeadlessWebViewViewModel - enum Constants { - static let navigationBarHideThreshold = 80.0 - } + let viewTitle = UserText.settingsPProSection + var purchaseURL = URL.subscriptionPurchase private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? - // State variables - var purchaseURL = URL.subscriptionPurchase - - enum FeatureName { - static let netP = "vpn" - static let itr = "identity-theft-restoration" - static let dbp = "personal-information-removal" + enum Constants { + static let navigationBarHideThreshold = 80.0 } - enum SubscriptionPurchaseError: Error { - case purchaseFailed, - missingEntitlements, - failedToGetSubscriptionOptions, - failedToSetSubscription, - failedToRestorePastPurchase, - subscriptionExpired, - hasActiveSubscription, - cancelledByUser, - generalError + + struct State { + var hasActiveSubscription = false + var transactionStatus: SubscriptionTransactionStatus = .idle + var userTappedRestoreButton = false + var shouldDismissView = false + var shouldActivateSubscription = false + var shouldShowNavigationBar: Bool = false + var canNavigateBack: Bool = false + var transactionError: SubscriptionPurchaseError? } - // Published properties - @Published var hasActiveSubscription = false - @Published var transactionStatus: SubscriptionTransactionStatus = .idle - @Published var userTappedRestoreButton = false - @Published var activateSubscriptionOnLoad: Bool = false - @Published var shouldDismissView = false - @Published var shouldShowNavigationBar: Bool = false - @Published var selectedFeature: SettingsViewModel.SettingsSection? - @Published var canNavigateBack: Bool = false - @Published var transactionError: SubscriptionPurchaseError? + // Publish the currently selected feature + @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? + + // Read only View State - Should only be modified from the VM + @Published private(set) var state = State() - private static let allowedDomains = [ - "duckduckgo.com", - "microsoftonline.com", - "duosecurity.com", - ] + private static let allowedDomains = [ "duckduckgo.com" ] private var webViewSettings = AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: allowedDomains, @@ -86,7 +70,7 @@ final class SubscriptionFlowViewModel: ObservableObject { init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), purchaseManager: PurchaseManager = PurchaseManager.shared, - selectedFeature: SettingsViewModel.SettingsSection? = nil) { + selectedFeature: SettingsViewModel.SettingsDeepLinkSection? = nil) { self.userScript = userScript self.subFeature = subFeature self.purchaseManager = purchaseManager @@ -108,38 +92,36 @@ final class SubscriptionFlowViewModel: ObservableObject { } } .store(in: &cancellables) - - subFeature.$activateSubscription - .receive(on: DispatchQueue.main) - .sink { [weak self] value in - if value { - self?.userTappedRestoreButton = true - } - } - .store(in: &cancellables) - subFeature.$selectedFeature - .receive(on: DispatchQueue.main) - .sink { [weak self] value in - if value != nil { - switch value?.feature { - case FeatureName.netP: - UniquePixel.fire(pixel: .privacyProWelcomeVPN) - self?.selectedFeature = .netP - case FeatureName.itr: - UniquePixel.fire(pixel: .privacyProWelcomePersonalInformationRemoval) - self?.selectedFeature = .itr - case FeatureName.dbp: - UniquePixel.fire(pixel: .privacyProWelcomeIdentityRestoration) - self?.selectedFeature = .dbp - default: - break - } - self?.finalizeSubscriptionFlow() - } + + subFeature.onBackToSettings = { + self.webViewModel.navigationCoordinator.navigateTo(url: URL.subscriptionPurchase) + } + + subFeature.onActivateSubscription = { + DispatchQueue.main.async { + self.state.shouldDismissView = true + self.state.shouldActivateSubscription = true } - .store(in: &cancellables) + } + + subFeature.onFeatureSelected = { feature in + DispatchQueue.main.async { + switch feature { + case .netP: + UniquePixel.fire(pixel: .privacyProWelcomeVPN) + self.selectedFeature = .netP + case .itr: + UniquePixel.fire(pixel: .privacyProWelcomePersonalInformationRemoval) + self.selectedFeature = .itr + case .dbp: + UniquePixel.fire(pixel: .privacyProWelcomeIdentityRestoration) + self.selectedFeature = .dbp + } + self.state.shouldDismissView = true + } + } subFeature.$transactionError .receive(on: DispatchQueue.main) @@ -147,14 +129,15 @@ final class SubscriptionFlowViewModel: ObservableObject { .sink { [weak self] value in guard let strongSelf = self else { return } if let value { - strongSelf.handleTransactionError(error: value) + Task { await strongSelf.handleTransactionError(error: value) } } } .store(in: &cancellables) } - // swiftlint:disable:next cyclomatic_complexity + // swiftlint:disable cyclomatic_complexity + @MainActor private func handleTransactionError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { var isStoreError = false @@ -163,39 +146,39 @@ final class SubscriptionFlowViewModel: ObservableObject { switch error { case .purchaseFailed: isStoreError = true - transactionError = .purchaseFailed + state.transactionError = .purchaseFailed case .missingEntitlements: isBackendError = true - transactionError = .missingEntitlements + state.transactionError = .missingEntitlements case .failedToGetSubscriptionOptions: isStoreError = true - transactionError = .failedToGetSubscriptionOptions + state.transactionError = .failedToGetSubscriptionOptions case .failedToSetSubscription: isBackendError = true - transactionError = .failedToSetSubscription + state.transactionError = .failedToSetSubscription case .failedToRestoreFromEmail, .failedToRestoreFromEmailSubscriptionInactive: isBackendError = true - transactionError = .generalError + state.transactionError = .generalError case .failedToRestorePastPurchase: isStoreError = true - transactionError = .failedToRestorePastPurchase + state.transactionError = .failedToRestorePastPurchase case .subscriptionNotFound: isStoreError = true - transactionError = .generalError + state.transactionError = .generalError case .subscriptionExpired: isStoreError = true - transactionError = .subscriptionExpired + state.transactionError = .subscriptionExpired case .hasActiveSubscription: isStoreError = true isBackendError = true - transactionError = .hasActiveSubscription + state.transactionError = .hasActiveSubscription case .cancelledByUser: - transactionError = nil + state.transactionError = nil case .accountCreationFailed: DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseFailureAccountNotCreated) - transactionError = .generalError + state.transactionError = .generalError default: - transactionError = .generalError + state.transactionError = .generalError } if isStoreError { @@ -206,12 +189,13 @@ final class SubscriptionFlowViewModel: ObservableObject { DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseFailureBackendError) } - if let transactionError, - transactionError != .hasActiveSubscription && transactionError != .cancelledByUser { + if state.transactionError != .hasActiveSubscription && + state.transactionError != .cancelledByUser { // The observer of `transactionError` does the same calculation, if the error is anything else than .hasActiveSubscription then shows a "Something went wrong" alert DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseFailure) } } + // swiftlint:enable cyclomatic_complexity private func setupWebViewObservers() async { webViewModel.$scrollPosition @@ -219,7 +203,7 @@ final class SubscriptionFlowViewModel: ObservableObject { .sink { [weak self] value in guard let strongSelf = self else { return } DispatchQueue.main.async { - strongSelf.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold + strongSelf.state.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold } } .store(in: &cancellables) @@ -229,7 +213,7 @@ final class SubscriptionFlowViewModel: ObservableObject { .sink { [weak self] error in guard let strongSelf = self else { return } DispatchQueue.main.async { - strongSelf.transactionError = error != nil ? .generalError : nil + strongSelf.state.transactionError = error != nil ? .generalError : nil } } @@ -239,60 +223,77 @@ final class SubscriptionFlowViewModel: ObservableObject { .receive(on: DispatchQueue.main) .sink { [weak self] value in guard let strongSelf = self else { return } - strongSelf.canNavigateBack = value + strongSelf.state.canNavigateBack = false + guard let currentURL = self?.webViewModel.url else { return } + if strongSelf.backButtonForURL(currentURL: currentURL) { + DispatchQueue.main.async { + strongSelf.state.canNavigateBack = value + } + } } } + private func backButtonForURL(currentURL: URL) -> Bool { + return currentURL != URL.subscriptionBaseURL.forComparison() && + currentURL != URL.subscriptionActivateSuccess.forComparison() && + currentURL != URL.subscriptionPurchase.forComparison() + } + @MainActor private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { - self.transactionStatus = status + self.state.transactionStatus = status } @MainActor - private func disableGoBack() { - canGoBackCancellable?.cancel() - canNavigateBack = false - } - - private func urlRemovingQueryParams(_ url: URL) -> URL? { - var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) - urlComponents?.query = nil // Remove the query string - return urlComponents?.url + private func backButtonEnabled(_ enabled: Bool) { + state.canNavigateBack = enabled } func initializeViewData() async { + Pixel.fire(pixel: .privacyProOfferScreenImpression, debounce: 2) await self.setupTransactionObserver() await self .setupWebViewObservers() - await self.updateSubscriptionStatus() - webViewModel.navigationCoordinator.navigateTo(url: purchaseURL ) } + @MainActor + func onAppear() { + resetState() + } + + @MainActor + func onDisappear() { + resetState() + } + + @MainActor + private func resetState() { + self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL ) + self.selectedFeature = nil + self.state.shouldDismissView = false + self.state.shouldActivateSubscription = false + } + + @MainActor func finalizeSubscriptionFlow() { - canGoBackCancellable?.cancel() - subFeature.selectedFeature = nil - hasActiveSubscription = false - transactionStatus = .idle - userTappedRestoreButton = false - shouldShowNavigationBar = false - selectedFeature = nil - transactionError = nil - canNavigateBack = false - shouldDismissView = true - subFeature.cleanup() + self.state.shouldDismissView = true } deinit { + canGoBackCancellable?.cancel() + selectedFeature = nil + subFeature.cleanup() cancellables.removeAll() } @MainActor func restoreAppstoreTransaction() { - transactionError = nil + clearTransactionError() Task { do { try await subFeature.restoreAccountFromAppStorePurchase() - disableGoBack() + backButtonEnabled(false) await webViewModel.navigationCoordinator.reload() + backButtonEnabled(true) } catch let error { if let specificError = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError { handleTransactionError(error: specificError) @@ -301,16 +302,15 @@ final class SubscriptionFlowViewModel: ObservableObject { } } - func updateSubscriptionStatus() async { - if AccountManager().isUserAuthenticated && hasActiveSubscription == false { - await disableGoBack() - await webViewModel.navigationCoordinator.reload() - } - } - @MainActor func navigateBack() async { await webViewModel.navigationCoordinator.goBack() } + + @MainActor + func clearTransactionError() { + state.transactionError = nil + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index d1c2f4e47d..4ed77dc2a4 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -31,16 +31,36 @@ final class SubscriptionRestoreViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature let purchaseManager: PurchaseManager let accountManager: AccountManager - var isAddingDevice: Bool + private var cancellables = Set() enum SubscriptionActivationResult { case unknown, activated, expired, notFound, error } - @Published var transactionStatus: SubscriptionTransactionStatus = .idle - @Published var activationResult: SubscriptionActivationResult = .unknown - @Published var subscriptionEmail: String? + struct State { + var isAddingDevice: Bool = false + var transactionStatus: SubscriptionTransactionStatus = .idle + var activationResult: SubscriptionActivationResult = .unknown + var subscriptionEmail: String? + var shouldShowWelcomePage = false + var shouldNavigateToActivationFlow = false + var shouldShowPlans = false + var shouldDismissView = false + + var viewTitle: String { + isAddingDevice ? UserText.subscriptionAddDeviceTitle : UserText.subscriptionActivate + } + } + + // Publish the currently selected feature + @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? + + // Read only View State - Should only be modified from the VM + @Published private(set) var state = State() + + // Email View Model + var emailViewModel = SubscriptionEmailViewModel() init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), @@ -51,17 +71,32 @@ final class SubscriptionRestoreViewModel: ObservableObject { self.subFeature = subFeature self.purchaseManager = purchaseManager self.accountManager = accountManager - self.isAddingDevice = isAddingDevice + self.state.isAddingDevice = false } func initializeView() { - Task { await updateAccountEmail() } Pixel.fire(pixel: .privacyProSettingsAddDevice) - subscriptionEmail = accountManager.email + Task { await setupTransactionObserver() } + } + + @MainActor + func onAppear() { + resetState() + } + + @MainActor + private func resetState() { + state.subscriptionEmail = accountManager.email + + state.isAddingDevice = false if accountManager.isUserAuthenticated { - isAddingDevice = true + state.isAddingDevice = true } - Task { await setupTransactionObserver() } + + state.shouldNavigateToActivationFlow = false + state.shouldShowPlans = false + state.shouldShowWelcomePage = false + state.shouldDismissView = false } private func setupTransactionObserver() async { @@ -82,16 +117,16 @@ final class SubscriptionRestoreViewModel: ObservableObject { private func handleRestoreError(error: SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError) { switch error { case .failedToRestorePastPurchase: - activationResult = .error + state.activationResult = .error case .subscriptionExpired: - activationResult = .expired + state.activationResult = .expired case .subscriptionNotFound: - activationResult = .notFound + state.activationResult = .notFound default: - activationResult = .error + state.activationResult = .error } - if activationResult == .notFound { + if state.activationResult == .notFound { DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseStoreFailureNotFound) } else { DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseStoreFailureOther) @@ -100,35 +135,47 @@ final class SubscriptionRestoreViewModel: ObservableObject { @MainActor private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { - self.transactionStatus = status + self.state.transactionStatus = status } @MainActor func restoreAppstoreTransaction() { DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseStoreStart) Task { - activationResult = .unknown + state.transactionStatus = .restoring + state.activationResult = .unknown do { try await subFeature.restoreAccountFromAppStorePurchase() DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseStoreSuccess) - activationResult = .activated + state.activationResult = .activated + state.transactionStatus = .idle } catch let error { if let specificError = error as? SubscriptionPagesUseSubscriptionFeature.UseSubscriptionError { handleRestoreError(error: specificError) } + state.transactionStatus = .idle } } } @MainActor - private func updateAccountEmail() async { - if let token = accountManager.authToken, - case let .success(accountDetails) = await accountManager.fetchAccountDetails(with: token) { - accountManager.storeAccount(token: token, email: accountDetails.email, externalID: accountDetails.externalID) - subscriptionEmail = accountDetails.email + func showActivationFlow(_ visible: Bool) { + if visible != state.shouldDismissView { + self.state.shouldNavigateToActivationFlow = visible } } + @MainActor + func showPlans() { + state.shouldShowPlans = true + state.shouldDismissView = true + } + + @MainActor + func dismissView() { + state.shouldDismissView = true + } + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 73fe6f48a8..9b306c7bf3 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -32,17 +32,30 @@ final class SubscriptionSettingsViewModel: ObservableObject { private var signOutObserver: Any? private var subscriptionInfo: SubscriptionService.GetSubscriptionResponse? - @Published var subscriptionDetails: String = "" - @Published var subscriptionType: String = "" - @Published var shouldDisplayRemovalNotice: Bool = false - @Published var shouldDismissView: Bool = false - @Published var shouldDisplayGoogleView: Bool = false - - // Used to display stripe WebUI - @Published var stripeViewModel: SubscriptionExternalLinkViewModel? - @Published var shouldDisplayStripeView: Bool = false private var externalAllowedDomains = ["stripe.com"] + struct State { + var subscriptionDetails: String = "" + var subscriptionType: String = "" + var shouldDisplayRemovalNotice: Bool = false + var shouldDismissView: Bool = false + var shouldDisplayGoogleView: Bool = false + var shouldDisplayFAQView: Bool = false + + // Used to display stripe WebUI + var stripeViewModel: SubscriptionExternalLinkViewModel? + var shouldDisplayStripeView: Bool = false + + // Used to display the FAQ WebUI + var FAQViewModel: SubscriptionExternalLinkViewModel = SubscriptionExternalLinkViewModel(url: URL.subscriptionFAQ) + } + + // Publish the currently selected feature + @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? + + // Read only View State - Should only be modified from the VM + @Published private(set) var state = State() + init(accountManager: AccountManager = AccountManager()) { self.accountManager = accountManager @@ -71,7 +84,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { billingPeriod: subscription.billingPeriod) case .failure: AccountManager().signOut() - shouldDismissView = true + state.shouldDismissView = true } } } @@ -81,7 +94,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { case .apple: Task { await manageAppleSubscription() } case .google: - manageGoogleSubscription() + displayGoogleView(true) case .stripe: Task { await manageStripeSubscription() } default: @@ -94,7 +107,7 @@ final class SubscriptionSettingsViewModel: ObservableObject { private func setupNotificationObservers() { signOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { [weak self] _ in DispatchQueue.main.async { - self?.shouldDismissView = true + self?.state.shouldDismissView = true } } } @@ -112,8 +125,8 @@ final class SubscriptionSettingsViewModel: ObservableObject { private func updateSubscriptionsStatusMessage(status: Subscription.Status, date: Date, product: String, billingPeriod: Subscription.BillingPeriod) { let statusString = (status == .autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires - self.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) - self.subscriptionType = billingPeriod == .monthly ? UserText.subscriptionMonthly : UserText.subscriptionAnnual + state.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) + state.subscriptionType = billingPeriod == .monthly ? UserText.subscriptionMonthly : UserText.subscriptionAnnual } func removeSubscription() { @@ -123,6 +136,32 @@ final class SubscriptionSettingsViewModel: ObservableObject { presentationLocation: .withoutBottomBar) } + func displayGoogleView(_ value: Bool) { + if value != state.shouldDisplayGoogleView { + state.shouldDisplayGoogleView = value + } + } + + func displayStripeView(_ value: Bool) { + if value != state.shouldDisplayStripeView { + state.shouldDisplayStripeView = value + } + } + + func displayRemovalNotice(_ value: Bool) { + if value != state.shouldDisplayRemovalNotice { + state.shouldDisplayRemovalNotice = value + } + } + + func displayFAQView(_ value: Bool) { + if value != state.shouldDisplayFAQView { + state.shouldDisplayFAQView = value + } + } + + // MARK: - + @MainActor private func manageAppleSubscription() async { let url = URL.manageSubscriptionsInAppStoreAppURL if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene { @@ -135,10 +174,6 @@ final class SubscriptionSettingsViewModel: ObservableObject { self.openURL(url) } } - - private func manageGoogleSubscription() { - shouldDisplayGoogleView = true - } private func manageStripeSubscription() async { guard let token = accountManager.accessToken, let externalID = accountManager.externalID else { return } @@ -147,17 +182,17 @@ final class SubscriptionSettingsViewModel: ObservableObject { // Get Stripe Customer Portal URL and update the model if case .success(let response) = serviceResponse { guard let url = URL(string: response.customerPortalUrl) else { return } - if let existingModel = stripeViewModel { + if let existingModel = state.stripeViewModel { existingModel.url = url } else { let model = SubscriptionExternalLinkViewModel(url: url, allowedDomains: externalAllowedDomains) DispatchQueue.main.async { - self.stripeViewModel = model + self.state.stripeViewModel = model } } } DispatchQueue.main.async { - self.shouldDisplayStripeView = true + self.displayStripeView(true) } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index a8b3d72c83..99fe2c826a 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -27,59 +27,124 @@ struct SubscriptionEmailView: View { @StateObject var viewModel = SubscriptionEmailViewModel() @Environment(\.dismiss) var dismiss - @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding - @State private var isActive: Bool = false - @State var isAddingDevice = false + @State var shouldDisplayInactiveError = false + @State var shouldDisplayNavigationError = false + @State var isModal = true + + var onDismissStack: (() -> Void)? + enum Constants { + static let navButtonPadding: CGFloat = 20.0 + static let backButtonImage = "chevron.left" + } + var body: some View { - ZStack { - VStack { - AsyncHeadlessWebView(viewModel: viewModel.webViewModel) - .background() + baseView + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + browserBackButton + } + ToolbarItem(placement: .navigationBarTrailing) { + closeButton } } + .navigationBarTitleDisplayMode(.inline) + .navigationViewStyle(.stack) + .navigationBarBackButtonHidden(true) + .tint(Color.init(designSystemColor: .textPrimary)) + .accentColor(Color.init(designSystemColor: .textPrimary)) .alert(isPresented: $shouldDisplayInactiveError) { Alert( title: Text(UserText.subscriptionRestoreEmailInactiveTitle), message: Text(UserText.subscriptionRestoreEmailInactiveMessage), dismissButton: .default(Text(UserText.actionOK)) { - dismiss() + viewModel.dismissView() } ) } - .alert(isPresented: $viewModel.navigationError) { + .alert(isPresented: $shouldDisplayNavigationError) { Alert( title: Text(UserText.subscriptionBackendErrorTitle), message: Text(UserText.subscriptionBackendErrorMessage), dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { - dismiss() + viewModel.dismissView() }) } .onAppear { - viewModel.loadURL() + viewModel.onAppear() + } + + .onChange(of: viewModel.state.shouldDisplayInactiveError) { value in + shouldDisplayInactiveError = value } + .onChange(of: viewModel.state.shouldDisplaynavigationError) { value in + shouldDisplayNavigationError = value + } - .onChange(of: viewModel.activateSubscription) { active in - if active { - // If updating email, just go back - if isAddingDevice { - dismiss() - } else { - // Pop to Root view - self.rootPresentationMode.wrappedValue.dismiss() + // Observe changes to shouldDismissView + .onChange(of: viewModel.state.shouldDismissView) { shouldDismiss in + if shouldDismiss { + dismiss() + + // Reset shouldDismissView after dismissal to ensure it can trigger again + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + viewModel.resetDismissalState() } } } - .onChange(of: viewModel.shouldDisplayInactiveError) { _ in - shouldDisplayInactiveError = true - } .navigationTitle(viewModel.viewTitle) + + .onAppear(perform: { + setUpAppearances() + viewModel.onAppear() + }) + + } + + // MARK: - + + @ViewBuilder + private var closeButton: some View { + if isModal { + Button(UserText.subscriptionCloseButton) { onDismissStack?() } + } + } + + private var baseView: some View { + ZStack { + VStack { + AsyncHeadlessWebView(viewModel: viewModel.webViewModel) + .background() + } + } + } + + @ViewBuilder + private var browserBackButton: some View { + if viewModel.shouldDisplayBackButton() { + Button(action: { + Task { await viewModel.navigateBack() } + }, label: { + HStack(spacing: 0) { + Image(systemName: Constants.backButtonImage) + Text(UserText.backButtonTitle).foregroundColor(Color(designSystemColor: .textPrimary)) + } + }) + } + } + + private func setUpAppearances() { + let navAppearance = UINavigationBar.appearance() + navAppearance.backgroundColor = UIColor(designSystemColor: .surface) + navAppearance.barTintColor = UIColor(designSystemColor: .surface) + navAppearance.shadowImage = UIImage() + navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 5df4f65ece..99ec18a506 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -28,12 +28,10 @@ struct SubscriptionFlowView: View { @Environment(\.dismiss) var dismiss @StateObject var viewModel = SubscriptionFlowViewModel() - @State private var shouldShowNavigationBar = false - @State private var isActive = false - @State private var transactionError: SubscriptionFlowViewModel.SubscriptionPurchaseError? + + // Local View State @State private var errorMessage: SubscriptionErrorMessage = .general @State private var shouldPresentError: Bool = false - @State private var isFirstOnAppear = true enum Constants { static let daxLogo = "Home" @@ -69,16 +67,17 @@ struct SubscriptionFlowView: View { } .edgesIgnoringSafeArea(.top) .navigationBarTitleDisplayMode(.inline) - .navigationBarHidden(!viewModel.shouldShowNavigationBar).animation(.easeOut) + .navigationBarHidden(!viewModel.state.shouldShowNavigationBar).animation(.easeOut) } .applyInsetGroupedListStyle() .tint(Color(designSystemColor: .textPrimary)) - .environment(\.rootPresentationMode, self.$isActive) } @ViewBuilder private var dismissButton: some View { - Button(action: { viewModel.finalizeSubscriptionFlow() }, label: { Text(UserText.subscriptionCloseButton) }) + Button(action: { + viewModel.finalizeSubscriptionFlow() + }, label: { Text(UserText.subscriptionCloseButton) }) .padding(Constants.navButtonPadding) .contentShape(Rectangle()) .tint(Color(designSystemColor: .textPrimary)) @@ -86,7 +85,7 @@ struct SubscriptionFlowView: View { @ViewBuilder private var backButton: some View { - if viewModel.canNavigateBack { + if viewModel.state.canNavigateBack { Button(action: { Task { await viewModel.navigateBack() } }, label: { @@ -100,7 +99,7 @@ struct SubscriptionFlowView: View { } private func getTransactionStatus() -> String { - switch viewModel.transactionStatus { + switch viewModel.state.transactionStatus { case .polling: return UserText.subscriptionCompletingPurchaseTitle case .purchasing: @@ -120,7 +119,7 @@ struct SubscriptionFlowView: View { // Show a dismiss button while the bar is not visible // But it should be hidden while performing a transaction - if !shouldShowNavigationBar && viewModel.transactionStatus == .idle { + if !viewModel.state.shouldShowNavigationBar && viewModel.state.transactionStatus == .idle { HStack { backButton.padding(.leading, Constants.navButtonPadding) Spacer() @@ -129,19 +128,13 @@ struct SubscriptionFlowView: View { } } - .onChange(of: viewModel.shouldDismissView) { result in + .onChange(of: viewModel.state.shouldDismissView) { result in if result { dismiss() - viewModel.shouldDismissView = false } } - - .onChange(of: viewModel.userTappedRestoreButton) { _ in - isActive = true - viewModel.userTappedRestoreButton = false - } - .onChange(of: viewModel.transactionError) { value in + .onChange(of: viewModel.state.transactionError) { value in if !shouldPresentError { let displayError: Bool = { @@ -167,27 +160,13 @@ struct SubscriptionFlowView: View { } .onAppear(perform: { - - if isFirstOnAppear && !viewModel.activateSubscriptionOnLoad { - isFirstOnAppear = false - Pixel.fire(pixel: .privacyProOfferScreenImpression) - } - setUpAppearances() Task { await viewModel.initializeViewData() } - - // Display the Restore page on load if required (With no animation) - if viewModel.activateSubscriptionOnLoad { - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.2) { - var transaction = Transaction() - transaction.disablesAnimations = true - withTransaction(transaction) { - isActive = true - viewModel.activateSubscriptionOnLoad = false - } - - } - } + viewModel.onAppear() + }) + + .onDisappear(perform: { + viewModel.onDisappear() }) .alert(isPresented: $shouldPresentError) { @@ -196,7 +175,7 @@ struct SubscriptionFlowView: View { } // The trailing close button should be hidden when a transaction is in progress - .navigationBarItems(trailing: viewModel.transactionStatus == .idle + .navigationBarItems(trailing: viewModel.state.transactionStatus == .idle ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } : nil) } @@ -209,8 +188,8 @@ struct SubscriptionFlowView: View { title: Text(UserText.subscriptionFoundTitle), message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { - viewModel.transactionError = nil - viewModel.finalizeSubscriptionFlow() + viewModel.clearTransactionError() + viewModel.finalizeSubscriptionFlow() }, secondaryButton: .default(Text(UserText.subscriptionFoundRestore)) { viewModel.restoreAppstoreTransaction() @@ -230,7 +209,6 @@ struct SubscriptionFlowView: View { message: Text(UserText.subscriptionBackendErrorMessage), dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { viewModel.finalizeSubscriptionFlow() - dismiss() } ) } @@ -240,21 +218,11 @@ struct SubscriptionFlowView: View { private var webView: some View { ZStack(alignment: .top) { - - // Restore View Hidden Link - let restoreView = SubscriptionRestoreView( - onDismissStack: { - viewModel.finalizeSubscriptionFlow() - dismiss() - }) - NavigationLink(destination: restoreView, isActive: $isActive) { - EmptyView() - }.isDetailLink(false) AsyncHeadlessWebView(viewModel: viewModel.webViewModel) .background() - if viewModel.transactionStatus != .idle { + if viewModel.state.transactionStatus != .idle { PurchaseInProgressView(status: getTransactionStatus()) } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 96e2653cf6..e411c109fd 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -152,7 +152,7 @@ struct SubscriptionITPView: View { if viewModel.isDownloadableContent { Button(action: { isShowingActivityView = true }, label: { Image(Constants.shareImage) }) .popover(isPresented: $isShowingActivityView, arrowEdge: .bottom) { - SubscriptionActivityViewController(activityItems: [viewModel.attachmentURL], applicationActivities: nil) + SubscriptionActivityViewController(activityItems: [viewModel.attachmentURL ?? ""], applicationActivities: nil) } } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift index 52f04501f9..6911051167 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift @@ -45,8 +45,8 @@ struct SubscriptionPIRView: View { static let headerPadding = 5.0 static let generalSpacing = 20.0 static let cornerRadius = 10.0 - static let windowsIcon = "Platform-Windows-16" - static let macOSIcon = "Platform-Apple-16" + static let windowsIcon = "Platform-Windows-16-subscriptions" + static let macOSIcon = "Platform-Apple-16-subscriptions" } var body: some View { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index c256d193f9..af5c7941d8 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -22,102 +22,171 @@ import SwiftUI import DesignResourcesKit import Core + #if SUBSCRIPTION @available(iOS 15.0, *) +// swiftlint:disable type_body_length struct SubscriptionRestoreView: View { @Environment(\.dismiss) var dismiss - @Environment(\.rootPresentationMode) private var rootPresentationMode: Binding @StateObject var viewModel = SubscriptionRestoreViewModel() - @State private var expandedItemId: Int = 0 + @State private var isAlertVisible = false - @State private var isActive: Bool = false - var onDismissStack: (() -> Void)? + @State private var shouldShowWelcomePage = false + @State private var shouldNavigateToActivationFlow = false + @State var isModal = true private enum Constants { static let heroImage = "ManageSubscriptionHero" - static let appleIDIcon = "Platform-Apple-16" + static let appleIDIcon = "Platform-Apple-16-subscriptions" static let emailIcon = "Email-16" - static let headerLineSpacing = 10.0 - static let viewStackSpacing = 20.0 - static let footerLineSpacing = 7.0 static let openIndicator = "chevron.up" static let closedIndicator = "chevron.down" + + static let viewPadding = EdgeInsets(top: 10, leading: 30, bottom: 0, trailing: 30) + static let sectionSpacing: CGFloat = 20 + static let maxWidth: CGFloat = 768 + static let boxMaxWidth: CGFloat = 500 + static let headerLineSpacing = 10.0 + static let footerLineSpacing = 7.0 + static let cornerRadius = 12.0 + static let boxPadding = EdgeInsets(top: 25, + leading: 20, + bottom: 25, + trailing: 20) + static let borderWidth: CGFloat = 1.0 + static let boxLineSpacing: CGFloat = 14 + static let buttonCornerRadius = 8.0 static let buttonInsets = EdgeInsets(top: 10.0, leading: 16.0, bottom: 10.0, trailing: 16.0) - static let cellLineSpacing = 12.0 - static let cellPadding = 20.0 - static let headerPadding = EdgeInsets(top: 16.0, leading: 30.0, bottom: 0, trailing: 30.0) - static let viewPadding: CGFloat = 18.0 - static let listPadding = EdgeInsets(top: 0, leading: 30, bottom: 0, trailing: 30) - static let borderWidth: CGFloat = 1.0 + static let buttonTopPadding: CGFloat = 20 + } var body: some View { - ZStack { - VStack(spacing: Constants.viewStackSpacing) { - - // Email Activation View Hidden link - NavigationLink(destination: SubscriptionEmailView(isAddingDevice: viewModel.isAddingDevice), isActive: $isActive) { - EmptyView() - }.isDetailLink(false) - - headerView - optionsView - footerView + if viewModel.state.isAddingDevice { + ZStack { + baseView + } + } else { + ZStack { - Spacer() - }.background(Color(designSystemColor: .background)) - - - .navigationTitle(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceTitle : UserText.subscriptionActivate) - .navigationBarBackButtonHidden(viewModel.transactionStatus != .idle) - .applyInsetGroupedListStyle() - - .alert(isPresented: $isAlertVisible) { getAlert() } - - .onChange(of: viewModel.activationResult) { result in - if result != .unknown { - isAlertVisible = true + NavigationView { + baseView + } + if viewModel.state.transactionStatus != .idle { + PurchaseInProgressView(status: getTransactionStatus()) } + } - .onAppear { - viewModel.initializeView() - setUpAppearances() + } + } + + @ViewBuilder + private var baseView: some View { + + Group { + ScrollView { + VStack(spacing: Constants.sectionSpacing) { + headerView + emailView + footerView + Spacer() + + // Hidden link to display Email Activation View + NavigationLink(destination: SubscriptionEmailView(viewModel: viewModel.emailViewModel, + isModal: isModal, + onDismissStack: { viewModel.dismissView() }), + isActive: $shouldNavigateToActivationFlow) { + EmptyView() + }.isDetailLink(false) + + }.frame(maxWidth: Constants.boxMaxWidth) } + .frame(maxWidth: Constants.maxWidth, alignment: .center) + .padding(Constants.viewPadding) + .background(Color(designSystemColor: .background)) + .tint(Color(designSystemColor: .icons)) - if viewModel.transactionStatus != .idle { - PurchaseInProgressView(status: getTransactionStatus()) + .navigationTitle(viewModel.state.viewTitle) + .navigationBarBackButtonHidden(viewModel.state.transactionStatus != .idle) + .navigationBarTitleDisplayMode(.inline) + .applyInsetGroupedListStyle() + .navigationBarItems(trailing: closeButton) + .tint(Color.init(designSystemColor: .textPrimary)) + .accentColor(Color.init(designSystemColor: .textPrimary)) + } + + .alert(isPresented: $isAlertVisible) { getAlert() } + + .onChange(of: viewModel.state.activationResult) { result in + if result != .unknown { + isAlertVisible = true + } + } + + // Navigation Flow Binding + .onChange(of: viewModel.state.shouldNavigateToActivationFlow) { result in + shouldNavigateToActivationFlow = result + } + .onChange(of: shouldNavigateToActivationFlow) { result in + viewModel.showActivationFlow(result) + } + + .onChange(of: viewModel.state.shouldDismissView) { result in + if result { + dismiss() + } + } + + .onChange(of: viewModel.state.shouldShowPlans) { result in + if result { + dismiss() } } + .onAppear { + viewModel.initializeView() + viewModel.onAppear() + setUpAppearances() + } + } - private var listItems: [ListItem] { - [ - .init(id: 0, - content: getCellTitle(icon: Constants.emailIcon, - text: UserText.subscriptionActivateEmail), - expandedContent: getEmailCellContent(buttonAction: { isActive = true })) - ] + + // MARK: - + + @ViewBuilder + private var closeButton: some View { + if isModal { + Button(UserText.subscriptionCloseButton) { viewModel.dismissView() } + } } - private func getCellTitle(icon: String, text: String) -> AnyView { - AnyView( + private var emailView: some View { + emailCellContent + .padding(Constants.boxPadding) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color(designSystemColor: .panel)) + .cornerRadius(Constants.cornerRadius) + .overlay( + RoundedRectangle(cornerRadius: Constants.cornerRadius) + .stroke(Color(designSystemColor: .lines), lineWidth: Constants.borderWidth) + ) + } + + private var emailCellContent: some View { + VStack(alignment: .leading, spacing: Constants.boxLineSpacing) { HStack { - Image(icon) - Text(text) + Image(Constants.emailIcon) + Text(UserText.subscriptionActivateEmail) .daxSubheadSemibold() .foregroundColor(Color(designSystemColor: .textPrimary)) } - ) - } - - private func getEmailCellContent(buttonAction: @escaping () -> Void) -> AnyView { - AnyView( + VStack(alignment: .leading) { - if !viewModel.isAddingDevice { + if !viewModel.state.isAddingDevice { Text(UserText.subscriptionActivateEmailDescription) .daxSubheadRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) @@ -125,53 +194,57 @@ struct SubscriptionRestoreView: View { action: { DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailStart) DailyPixel.fire(pixel: .privacyProWelcomeAddDevice) - buttonAction() + viewModel.showActivationFlow(true) }) - } else if viewModel.subscriptionEmail == nil { + } else if viewModel.state.subscriptionEmail == nil { Text(UserText.subscriptionAddDeviceEmailDescription) .daxSubheadRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) getCellButton(buttonText: UserText.subscriptionRestoreAddEmailButton, action: { - Pixel.fire(pixel: .privacyProAddDeviceEnterEmail) - buttonAction() + Pixel.fire(pixel: .privacyProAddDeviceEnterEmail, debounce: 1) + viewModel.showActivationFlow(true) }) } else { - Text(viewModel.subscriptionEmail ?? "").daxSubheadSemibold() + Text(viewModel.state.subscriptionEmail ?? "").daxSubheadSemibold() Text(UserText.subscriptionManageEmailDescription) .daxSubheadRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) HStack { getCellButton(buttonText: UserText.subscriptionManageEmailButton, action: { - Pixel.fire(pixel: .privacyProSubscriptionManagementEmail) - buttonAction() + Pixel.fire(pixel: .privacyProSubscriptionManagementEmail, debounce: 1) + viewModel.showActivationFlow(true) }) } } - }) + } + } } private func getCellButton(buttonText: String, action: @escaping () -> Void) -> AnyView { AnyView( - Button(action: action, label: { - Text(buttonText) - .daxButton() - .padding(Constants.buttonInsets) - .foregroundColor(.white) - .overlay( - RoundedRectangle(cornerRadius: Constants.buttonCornerRadius) - .stroke(Color.clear, lineWidth: 1) - ) - }) - .background(Color(designSystemColor: .accent)) - .cornerRadius(Constants.buttonCornerRadius) - .padding(.top, Constants.cellLineSpacing) + VStack { + Button(action: action, label: { + Text(buttonText) + .daxButton() + .padding(Constants.buttonInsets) + .foregroundColor(.white) + .overlay( + RoundedRectangle(cornerRadius: Constants.buttonCornerRadius) + .stroke(Color.clear, lineWidth: 1) + ) + }) + + .background(Color(designSystemColor: .accent)) + .cornerRadius(Constants.buttonCornerRadius) + }.padding(.top, Constants.boxLineSpacing) + ) } private func getTransactionStatus() -> String { - switch viewModel.transactionStatus { + switch viewModel.state.transactionStatus { case .polling: return UserText.subscriptionCompletingPurchaseTitle case .purchasing: @@ -185,103 +258,65 @@ struct SubscriptionRestoreView: View { private var headerView: some View { VStack(spacing: Constants.headerLineSpacing) { - Image(Constants.heroImage).padding(.bottom, Constants.cellLineSpacing) - Text(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceHeaderTitle : UserText.subscriptionActivateTitle) + Image(Constants.heroImage) + Text(viewModel.state.isAddingDevice ? UserText.subscriptionAddDeviceHeaderTitle : UserText.subscriptionActivateTitle) .daxHeadline() .multilineTextAlignment(.center) .foregroundColor(Color(designSystemColor: .textPrimary)) - Text(viewModel.isAddingDevice ? UserText.subscriptionAddDeviceDescription : UserText.subscriptionActivateHeaderDescription) + Text(viewModel.state.isAddingDevice ? UserText.subscriptionAddDeviceDescription : UserText.subscriptionActivateHeaderDescription) .daxFootnoteRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) .multilineTextAlignment(.center) } - .padding(Constants.headerPadding) + } @ViewBuilder private var footerView: some View { - if !viewModel.isAddingDevice { + if !viewModel.state.isAddingDevice { VStack(alignment: .leading, spacing: Constants.footerLineSpacing) { Text(UserText.subscriptionActivateDescription) .daxFootnoteRegular() .foregroundColor(Color(designSystemColor: .textSecondary)) Button(action: { - viewModel.restoreAppstoreTransaction() + viewModel.restoreAppstoreTransaction() }, label: { Text(UserText.subscriptionRestoreAppleID) .daxFootnoteSemibold() .foregroundColor(Color(designSystemColor: .accent)) }) } - .padding(Constants.listPadding) - } - } - - private var optionsView: some View { - VStack { - ForEach(Array(zip(listItems.indices, listItems)), id: \.1.id) { _, item in - VStack(alignment: .leading, spacing: Constants.cellLineSpacing) { - HStack { - item.content - Spacer() - if listItems.count > 1 { - Image(systemName: expandedItemId == item.id ? Constants.openIndicator : Constants.closedIndicator) - .foregroundColor(Color(designSystemColor: .textPrimary)) - } - } - .contentShape(Rectangle()) - .onTapGesture { - expandedItemId = expandedItemId == item.id ? 0 : item.id - } - if expandedItemId == item.id { - item.expandedContent - } - } - .padding(Constants.cellPadding) - .background(Color(designSystemColor: .panel)) - .cornerRadius(Constants.cornerRadius) - .overlay( - RoundedRectangle(cornerRadius: Constants.cornerRadius) - .stroke(Color(designSystemColor: .lines), lineWidth: Constants.borderWidth) - ) - .padding(Constants.listPadding) - } } } - + private func getAlert() -> Alert { - switch viewModel.activationResult { + switch viewModel.state.activationResult { case .activated: return Alert(title: Text(UserText.subscriptionRestoreSuccessfulTitle), message: Text(UserText.subscriptionRestoreSuccessfulMessage), dismissButton: .default(Text(UserText.subscriptionRestoreSuccessfulButton)) { - dismiss() + viewModel.dismissView() } ) case .notFound: return Alert(title: Text(UserText.subscriptionRestoreNotFoundTitle), message: Text(UserText.subscriptionRestoreNotFoundMessage), primaryButton: .default(Text(UserText.subscriptionRestoreNotFoundPlans), - action: { - dismiss() - }), + action: { viewModel.showPlans() }), secondaryButton: .cancel()) case .expired: return Alert(title: Text(UserText.subscriptionRestoreNotFoundTitle), message: Text(UserText.subscriptionRestoreNotFoundMessage), primaryButton: .default(Text(UserText.subscriptionRestoreNotFoundPlans), - action: { - dismiss() - }), + action: { viewModel.showPlans() }), secondaryButton: .cancel()) default: return Alert( title: Text(UserText.subscriptionBackendErrorTitle), message: Text(UserText.subscriptionBackendErrorMessage), dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { - onDismissStack?() - dismiss() + viewModel.dismissView() } ) } @@ -294,7 +329,7 @@ struct SubscriptionRestoreView: View { navAppearance.shadowImage = UIImage() navAppearance.tintColor = UIColor(designSystemColor: .textPrimary) } - + struct ListItem { let id: Int let content: AnyView @@ -302,21 +337,5 @@ struct SubscriptionRestoreView: View { } } - -@available(iOS 15.0, *) -struct SubscriptionRestoreView_Previews: PreviewProvider { - static var previews: some View { - SubscriptionRestoreView() - .previewDevice("iPhone 12") - } -} - -// Commented out because CI fails if a SwiftUI preview is enabled https://app.asana.com/0/414709148257752/1206774081310425/f -// @available(iOS 15.0, *) -// struct SubscriptionRestoreView_Previews: PreviewProvider { -// static var previews: some View { -// SubscriptionRestoreView() -// } -// } - +// swiftlint:enable type_body_length #endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index 5760a2014a..098ca6aa6b 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -34,86 +34,153 @@ struct SubscriptionSettingsView: View { @Environment(\.dismiss) var dismiss @StateObject var viewModel = SubscriptionSettingsViewModel() @StateObject var sceneEnvironment = SceneEnvironment() - @State var isFirstOnAppear = true - @ViewBuilder - private var optionsView: some View { - List { - Section { - VStack(alignment: .center, spacing: 7) { - Image("Privacy-Pro-96x96") - Text(UserText.subscriptionTitle).daxTitle2() - Text(viewModel.subscriptionType).daxHeadline() - Text(viewModel.subscriptionDetails) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - } + @State var shouldDisplayStripeView = false + @State var shouldDisplayGoogleView = false + @State var shouldDisplayRemovalNotice = false + @State var shouldDisplayFAQView = false + + var body: some View { + optionsView + .onAppear(perform: { + Pixel.fire(pixel: .privacyProSubscriptionSettings, debounce: 1) + }) + .navigationBarTitleDisplayMode(.inline) + } + + // MARK: - + + private var headerSection: some View { + Section { + VStack(alignment: .center, spacing: 7) { + Image("Privacy-Pro-96x96") + Text(UserText.subscriptionTitle).daxTitle2() + Text(viewModel.state.subscriptionType).daxHeadline() + Text(viewModel.state.subscriptionDetails) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) } - .listRowBackground(Color.clear) - .frame(maxWidth: .infinity, alignment: .center) + } + .listRowBackground(Color.clear) + .frame(maxWidth: .infinity, alignment: .center) + + } + + private var manageSection: some View { + Section(header: Text(UserText.subscriptionManageTitle)) { + SettingsCustomCell(content: { + Text(UserText.subscriptionChangePlan) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent)) + }, + action: { + Pixel.fire(pixel: .privacyProSubscriptionManagementPlanBilling, debounce: 1) + Task { viewModel.manageSubscription() } + }, + isButton: true) + .sheet(isPresented: $shouldDisplayStripeView) { + if let stripeViewModel = viewModel.state.stripeViewModel { + SubscriptionExternalLinkView(viewModel: stripeViewModel, title: UserText.subscriptionManagePlan) + } + } + } + } + + private var devicesSection: some View { + Section(header: Text(UserText.subscriptionManageDevices)) { - Section(header: Text(UserText.subscriptionManageTitle)) { + NavigationLink(destination: SubscriptionRestoreView(isModal: false)) { SettingsCustomCell(content: { - Text(UserText.subscriptionChangePlan) + Text(UserText.subscriptionAddDeviceButton) .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent)) - }, - action: { - Pixel.fire(pixel: .privacyProSubscriptionManagementPlanBilling) - Task { viewModel.manageSubscription() } - }, - isButton: true) + }) } + + SettingsCustomCell(content: { + Text(UserText.subscriptionRemoveFromDevice) + .daxBodyRegular() + .foregroundColor(Color.init(designSystemColor: .accent))}, + action: { viewModel.displayRemovalNotice(true) }, + isButton: true) - .sheet(isPresented: $viewModel.shouldDisplayStripeView) { - if let stripeViewModel = viewModel.stripeViewModel { - SubscriptionExternalLinkView(viewModel: stripeViewModel, title: UserText.subscriptionManagePlan) - } - } + } + } + + @ViewBuilder var helpSection: some View { + Section(header: Text(UserText.subscriptionHelpAndSupport), + footer: Text(UserText.subscriptionFAQFooter)) { - Section(header: Text(UserText.subscriptionManageDevices)) { - - NavigationLink(destination: SubscriptionRestoreView()) { - SettingsCustomCell(content: { - Text(UserText.subscriptionAddDeviceButton) - .daxBodyRegular() - }) - } - - SettingsCustomCell(content: { - Text(UserText.subscriptionRemoveFromDevice) - .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent))}, - action: { viewModel.shouldDisplayRemovalNotice.toggle() }, - isButton: true) - - } + + SettingsCustomCell(content: { + Text(UserText.subscriptionFAQ) + .daxBodyRegular() + .foregroundColor(Color(designSystemColor: .accent)) + }, + action: { viewModel.displayFAQView(true) }, + disclosureIndicator: false, + isButton: true) - Section(header: Text(UserText.subscriptionHelpAndSupport), - footer: Text(UserText.subscriptionFAQFooter)) { - NavigationLink(destination: Text(UserText.subscriptionFAQ)) { - SettingsCustomCell(content: { - Text(UserText.subscriptionFAQ) - .daxBodyRegular() - }) - } - } + } + } + + @ViewBuilder + private var optionsView: some View { + NavigationLink(destination: SubscriptionGoogleView(), + isActive: $shouldDisplayGoogleView) { + EmptyView() + } + + List { + headerSection + manageSection + devicesSection + helpSection - NavigationLink(destination: SubscriptionGoogleView(), isActive: $viewModel.shouldDisplayGoogleView) { - EmptyView() - } } .navigationTitle(UserText.settingsPProManageSubscription) .applyInsetGroupedListStyle() - .onChange(of: viewModel.shouldDismissView) { value in + .onChange(of: viewModel.state.shouldDismissView) { value in if value { dismiss() } } + // Google Binding + .onChange(of: viewModel.state.shouldDisplayGoogleView) { value in + shouldDisplayGoogleView = value + } + .onChange(of: shouldDisplayGoogleView) { value in + viewModel.displayGoogleView(value) + } + + // Stripe Binding + .onChange(of: viewModel.state.shouldDisplayStripeView) { value in + shouldDisplayStripeView = value + } + .onChange(of: shouldDisplayStripeView) { value in + viewModel.displayStripeView(value) + } + + // Removal Notice + .onChange(of: viewModel.state.shouldDisplayRemovalNotice) { value in + shouldDisplayRemovalNotice = value + } + .onChange(of: shouldDisplayRemovalNotice) { value in + viewModel.displayRemovalNotice(value) + } + + // Removal Notice + .onChange(of: viewModel.state.shouldDisplayFAQView) { value in + shouldDisplayFAQView = value + } + .onChange(of: shouldDisplayFAQView) { value in + viewModel.displayFAQView(value) + } + + // Remove subscription - .alert(isPresented: $viewModel.shouldDisplayRemovalNotice) { + .alert(isPresented: $shouldDisplayRemovalNotice) { Alert( title: Text(UserText.subscriptionRemoveFromDeviceConfirmTitle), message: Text(UserText.subscriptionRemoveFromDeviceConfirmText), @@ -127,6 +194,10 @@ struct SubscriptionSettingsView: View { ) } + .sheet(isPresented: $shouldDisplayFAQView, content: { + SubscriptionExternalLinkView(viewModel: viewModel.state.FAQViewModel, title: UserText.subscriptionFAQ) + }) + .onAppear { viewModel.fetchAndUpdateSubscriptionDetails() } @@ -134,27 +205,11 @@ struct SubscriptionSettingsView: View { @ViewBuilder private var stripeView: some View { - if let stripeViewModel = viewModel.stripeViewModel { + if let stripeViewModel = viewModel.state.stripeViewModel { SubscriptionExternalLinkView(viewModel: stripeViewModel) } } - - var body: some View { - Group { - if #available(iOS 16.0, *) { - optionsView - .scrollDisabled(true) - } else { - optionsView - } - }.onAppear(perform: { - if isFirstOnAppear { - isFirstOnAppear = false - Pixel.fire(pixel: .privacyProSubscriptionSettings) - } - }) - .navigationBarTitleDisplayMode(.inline) - } + } #endif diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 7fd5c9a032..429420d8d0 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -234,7 +234,7 @@ final class SubscriptionDebugViewController: UITableViewController { private func getEntitlements() { Task { var results: [String] = [] - guard let token = accountManager.accessToken else { + guard accountManager.accessToken != nil else { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index fa1b65cba3..a66193ba40 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1089,6 +1089,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let subscriptionActivateAppleIDDescription = NSLocalizedString("subscription.activate.appleid.description", value: "Restore your purchase to activate your subscription on this device.", comment: "Description for Apple ID activation") public static let subscriptionRestoreAppleID = NSLocalizedString("subscription.activate.restore.apple", value: "Restore Purchase", comment: "Restore button title for AppleID") public static let subscriptionActivateEmail = NSLocalizedString("subscription.activate.email", value: "Email", comment: "Email option for activation") + public static let subscriptionActivateEmailTitle = NSLocalizedString("subscription.activate.email.title", value: "Activate Subscription", comment: "Activate subscription title") public static let subscriptionActivateEmailDescription = NSLocalizedString("subscription.activate.email.description", value: "Use your email to activate your subscription on this device.", comment: "Description for Email activation") public static let subscriptionAddDeviceEmailDescription = NSLocalizedString("subscription.addDevice.email.description", value: "Add an email address to access your subscription in DuckDuckGo on other devices. We’ll only use this address to verify your subscription.", comment: "Description for Email adding") public static let subscriptionAddEmailButton = NSLocalizedString("subscription.activate.add.email.button", value: "Add Email", comment: "Restore button title for Email") diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index be87fc63fa..63f9aaddd5 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -1983,6 +1983,9 @@ But if you *do* want a peek under the hood, you can find more information about /* Description for Email activation */ "subscription.activate.email.description" = "Use your email to activate your subscription on this device."; +/* Activate subscription title */ +"subscription.activate.email.title" = "Activate Subscription"; + /* Restore button title for Managing Email */ "subscription.activate.manage.email.button" = "Manage"; diff --git a/DuckDuckGoTests/PixelTests.swift b/DuckDuckGoTests/PixelTests.swift index e5f40eb6cf..0fd42f0d15 100644 --- a/DuckDuckGoTests/PixelTests.swift +++ b/DuckDuckGoTests/PixelTests.swift @@ -166,5 +166,37 @@ class PixelTests: XCTestCase { wait(for: [expectation], timeout: 1.0) } + + func testPixelDebouncePreventsFiringWithinInterval() { + let firstFireExpectation = XCTestExpectation(description: "First pixel fire should succeed") + let thirdFireExpectation = XCTestExpectation(description: "Third pixel fire should succeed after debounce interval") + + stub(condition: isHost(self.host)) { _ -> HTTPStubsResponse in + return HTTPStubsResponse(data: Data(), statusCode: 200, headers: nil) + } + + let pixel = Pixel.Event.appLaunch + let debounceInterval = 1 // Debounce interval of 5 seconds + + // Should be OK + Pixel.fire(pixel: pixel, forDeviceType: .phone, onComplete: { error in + XCTAssertNil(error) + firstFireExpectation.fulfill() + }, debounce: debounceInterval) + + // Should be debounced + Pixel.fire(pixel: pixel, forDeviceType: .phone, onComplete: { _ in + }, debounce: debounceInterval) + + // Should be OK + DispatchQueue.main.asyncAfter(deadline: .now() + DispatchTimeInterval.seconds(debounceInterval + 1)) { + Pixel.fire(pixel: pixel, forDeviceType: .phone, onComplete: { error in + XCTAssertNil(error) + thirdFireExpectation.fulfill() + }, debounce: debounceInterval) + } + + wait(for: [firstFireExpectation, thirdFireExpectation], timeout: Double(debounceInterval + 2)) + } } diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..a603ff9af2 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit a603ff9af22ca3ff7ce2e7ffbfe18c447d9f23e8 From eeb1dfa531046e81f524e68a13df7657b099758d Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Sun, 17 Mar 2024 08:37:01 +0100 Subject: [PATCH 129/245] Fixes the name of a pixel (#2575) Task/Issue URL: https://app.asana.com/0/1199230911884351/1206819771949640/f ## Description One of the pixel names was off as it includes the word "mac". --- Core/PixelEvent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 78521372c7..472635c170 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -861,7 +861,7 @@ extension Pixel.Event { case .networkProtectionTunnelFailureRecovered: return "m_netp_ev_tunnel_failure_recovered" case .networkProtectionLatency(let quality): return "m_netp_ev_\(quality.rawValue)_latency" case .networkProtectionLatencyError: return "m_netp_ev_latency_error_d" - case .networkProtectionRekeyAttempt: return "m_mac_netp_rekey_attempt" + case .networkProtectionRekeyAttempt: return "m_netp_rekey_attempt" case .networkProtectionRekeyCompleted: return "m_netp_rekey_completed" case .networkProtectionRekeyFailure: return "m_netp_rekey_failure" case .networkProtectionEnabledOnSearch: return "m_netp_ev_enabled_on_search" From 613bcda7815121f50b4ec822afb7d5550c6e5ba8 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Sun, 17 Mar 2024 14:51:45 -0700 Subject: [PATCH 130/245] Fix VPN feedback form description (#2606) Task/Issue URL: https://app.asana.com/0/414235014887631/1206855925194478/f Tech Design URL: CC: Description: This PR fixes an issue where the feedback form wasn't always sending the description. This issue seems to be because of the way we were handling ownership of the view model, it was being created inline but only being referenced by the view itself with @ObservedObject. Instead we want to have the feedback form view own the view model using @State. --- DuckDuckGo/Feedback/VPNFeedbackFormView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index c1ea9968bf..f61d812501 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -79,7 +79,7 @@ struct VPNFeedbackFormCategoryView: View { @available(iOS 15.0, *) struct VPNFeedbackFormView: View { - @ObservedObject var viewModel: VPNFeedbackFormViewModel + @StateObject var viewModel: VPNFeedbackFormViewModel @Environment(\.dismiss) private var dismiss @FocusState private var isTextEditorFocused: Bool From 0dbf447bade96443d54e1c90ef56a1804f2fb44a Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Sun, 17 Mar 2024 23:48:02 +0100 Subject: [PATCH 131/245] Updates BSK to include latest changes (#2604) Task/Issue URL: https://app.asana.com/0/0/1206857816167860/f macOS: https://github.com/duckduckgo/macos-browser/pull/2434 BSK: https://github.com/duckduckgo/BrowserServicesKit/pull/732 ## Description Updates BSK to include the latest changes. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f704909b8b..d5cf75b08a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10028,7 +10028,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 125.0.0; + version = 125.0.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 137531c0e4..c37398c8d0 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" : "ac2127a26f75b2aa293f6036bcdd2bc241d09819", - "version" : "125.0.0" + "revision" : "eced8f93c945ff2fa4ff92bdd619514d4eff7131", + "version" : "125.0.1" } }, { From daa708c742180d9399ed27fc463122af921cd5ba Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Sun, 17 Mar 2024 18:44:14 -0700 Subject: [PATCH 132/245] Update wireguard-apple to 1.1.3 (#2598) Task/Issue URL: https://app.asana.com/0/414235014887631/1206848206360819/f Tech Design URL: CC: Description: Client PR for duckduckgo/BrowserServicesKit#729. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d5cf75b08a..ef7ff84466 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10028,7 +10028,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 125.0.1; + version = 125.0.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c37398c8d0..f2336cc993 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" : "eced8f93c945ff2fa4ff92bdd619514d4eff7131", - "version" : "125.0.1" + "revision" : "810bf41347ff437b5d0154405a238553537240a4", + "version" : "125.0.2" } }, { @@ -194,8 +194,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/wireguard-apple", "state" : { - "revision" : "2d8172c11478ab11b0f5ad49bdb4f93f4b3d5e0d", - "version" : "1.1.1" + "revision" : "13fd026384b1af11048451061cc1b21434990668", + "version" : "1.1.3" } }, { From 98203852a9879b8af4815efcaefbd1fca89797d7 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 18 Mar 2024 12:53:34 +0000 Subject: [PATCH 133/245] Use History in Suggestions on iOS (#2552) Task/Issue URL: https://app.asana.com/0/0/1206524433066958/f Tech Design URL: CC: Description: Surfaces history in the autocomplete. Steps to test this PR: Perform a search Start typing part of the search, it should appear as a history item (with the clock icon) at the bottom of suggestions Browse to some sites Start typing part of some of the site titles, they should appear as history items at the bottom of the suggestions Visit a couple of pages several times (ie more then 3) Start typing part of those page titles, they should appear as history items at the top Add bookmarks and favourites and try again. Appropriate suggestions should be made. Use the fire button, history should no longer appear in suggestions. --- Core/BookmarksCachingSearch.swift | 52 +++-- Core/Debounce.swift | 39 ---- Core/DefaultVariantManager.swift | 4 + Core/HistoryCapture.swift | 9 +- Core/PixelEvent.swift | 24 ++- DuckDuckGo.xcodeproj/project.pbxproj | 54 ++--- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../xcschemes/DuckDuckGo.xcscheme | 2 +- DuckDuckGo/AutocompleteRequest.swift | 95 --------- DuckDuckGo/AutocompleteViewController.swift | 188 +++++++++++------- .../AutocompleteViewControllerDelegate.swift | 1 + DuckDuckGo/Base.lproj/Autocomplete.storyboard | 48 +++-- DuckDuckGo/BlankSnapshotViewController.swift | 1 + DuckDuckGo/CachedBookmarkSuggestions.swift | 61 ++++++ DuckDuckGo/MainViewController.swift | 68 +++++-- DuckDuckGo/OmniBarDelegate.swift | 1 + DuckDuckGo/SettingsCustomizeView.swift | 1 + DuckDuckGo/SettingsViewModel.swift | 10 +- DuckDuckGo/Suggestion.swift | 41 ---- DuckDuckGo/SuggestionTableViewCell.swift | 54 ++++- DuckDuckGo/SuggestionTrayViewController.swift | 17 +- DuckDuckGo/UserText.swift | 4 +- .../AutocompleteRequestTests.swift | 106 ---------- DuckDuckGoTests/HistoryCaptureTests.swift | 14 -- submodules/privacy-reference-tests | 2 +- 25 files changed, 409 insertions(+), 491 deletions(-) delete mode 100644 Core/Debounce.swift delete mode 100644 DuckDuckGo/AutocompleteRequest.swift create mode 100644 DuckDuckGo/CachedBookmarkSuggestions.swift delete mode 100644 DuckDuckGo/Suggestion.swift delete mode 100644 DuckDuckGoTests/AutocompleteRequestTests.swift diff --git a/Core/BookmarksCachingSearch.swift b/Core/BookmarksCachingSearch.swift index af6ea20826..a874a05284 100644 --- a/Core/BookmarksCachingSearch.swift +++ b/Core/BookmarksCachingSearch.swift @@ -60,19 +60,7 @@ public class CoreDataBookmarksSearchStore: BookmarksSearchStore { public func bookmarksAndFavorites(completion: @escaping ([BookmarksCachingSearch.ScoredBookmark]) -> Void) { let context = bookmarksStore.makeContext(concurrencyType: .privateQueueConcurrencyType) - - let fetchRequest = NSFetchRequest(entityName: "BookmarkEntity") - fetchRequest.predicate = NSPredicate( - format: "%K = false AND %K == NO AND (%K == NO OR %K == nil)", - #keyPath(BookmarkEntity.isFolder), - #keyPath(BookmarkEntity.isPendingDeletion), - #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub) - ) - fetchRequest.resultType = .dictionaryResultType - fetchRequest.propertiesToFetch = [#keyPath(BookmarkEntity.title), - #keyPath(BookmarkEntity.url), - #keyPath(BookmarkEntity.objectID)] - fetchRequest.relationshipKeyPathsForPrefetching = [#keyPath(BookmarkEntity.favoriteFolders)] + let fetchRequest = Self.shallowBookmarksFetchRequest(context: context) context.perform { let result = try? context.fetch(fetchRequest) as? [[String: Any]] @@ -97,6 +85,38 @@ public class CoreDataBookmarksSearchStore: BookmarksSearchStore { externalContext.persistentStoreCoordinator == bookmarksStore.coordinator else { return } subject.send() } + + /// Creates an `NSFetchRequest` to retrieve each bookmark as a shallow dictionary. + /// + /// The dictionary contains + /// * `#keyPath(BookmarkEntity.title)` + /// * `#keyPath(BookmarkEntity.url)` + /// * `#keyPath(BookmarkEntity.objectID)` + /// * `#keyPath(BookmarkEntity.favoriteFolders)` + /// + /// Note that is `#keyPath(BookmarkEntity.favoriteFolders)` an `Int` representing the count of favorites folders this bookmark is contained in + public static func shallowBookmarksFetchRequest(context: NSManagedObjectContext) -> NSFetchRequest { + let favExpression = NSExpressionDescription() + favExpression.name = #keyPath(BookmarkEntity.favoriteFolders) + favExpression.expression = NSExpression(forFunction: "count:", + arguments: [NSExpression(forKeyPath: #keyPath(BookmarkEntity.favoriteFolders))]) + favExpression.expressionResultType = .integer64AttributeType + + let fetchRequest = NSFetchRequest(entityName: "BookmarkEntity") + fetchRequest.predicate = NSPredicate( + format: "%K = false AND %K == NO AND (%K == NO OR %K == nil)", + #keyPath(BookmarkEntity.isFolder), + #keyPath(BookmarkEntity.isPendingDeletion), + #keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub) + ) + fetchRequest.resultType = .dictionaryResultType + fetchRequest.propertiesToFetch = [#keyPath(BookmarkEntity.title), + #keyPath(BookmarkEntity.url), + #keyPath(BookmarkEntity.objectID), + favExpression] + + return fetchRequest + } } public class BookmarksCachingSearch: BookmarksStringSearch { @@ -125,14 +145,16 @@ public class BookmarksCachingSearch: BookmarksStringSearch { guard let title = bookmark[#keyPath(BookmarkEntity.title)] as? String, let urlString = bookmark[#keyPath(BookmarkEntity.url)] as? String, let url = URL(string: urlString), - let objectID = bookmark[#keyPath(BookmarkEntity.objectID)] as? NSManagedObjectID else { + let objectID = bookmark[#keyPath(BookmarkEntity.objectID)] as? NSManagedObjectID, + let favoritesFolderCount = bookmark[#keyPath(BookmarkEntity.favoriteFolders)] as? Int + else { return nil } self.init(objectID: objectID, title: title, url: url, - isFavorite: (bookmark[#keyPath(BookmarkEntity.favoriteFolders)] as? Set)?.isEmpty != true) + isFavorite: favoritesFolderCount > 0) } public func togglingFavorite() -> BookmarksStringSearchResult { diff --git a/Core/Debounce.swift b/Core/Debounce.swift deleted file mode 100644 index 6994c3dda3..0000000000 --- a/Core/Debounce.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// Debounce.swift -// DuckDuckGo -// -// Copyright © 2019 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 - -public class Debounce { - - private let queue: DispatchQueue - private let interval: TimeInterval - - private var currentWorkItem = DispatchWorkItem(block: {}) - - public init(queue: DispatchQueue, seconds: TimeInterval) { - self.queue = queue - self.interval = seconds - } - - public func schedule(_ block: @escaping (() -> Void)) { - currentWorkItem.cancel() - currentWorkItem = DispatchWorkItem(block: { block() }) - queue.asyncAfter(deadline: .now() + interval, execute: currentWorkItem) - } -} diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift index b17b8de45d..98cbf59382 100644 --- a/Core/DefaultVariantManager.swift +++ b/Core/DefaultVariantManager.swift @@ -62,6 +62,10 @@ public struct VariantIOS: Variant { VariantIOS(name: "sc", weight: doNotAllocate, isIncluded: When.always, features: []), VariantIOS(name: "sd", weight: doNotAllocate, isIncluded: When.always, features: []), VariantIOS(name: "se", weight: doNotAllocate, isIncluded: When.always, features: []), + + VariantIOS(name: "mc", weight: 1, isIncluded: When.inEnglish, features: []), + VariantIOS(name: "md", weight: 1, isIncluded: When.inEnglish, features: [.history]), + returningUser ] diff --git a/Core/HistoryCapture.swift b/Core/HistoryCapture.swift index d63c7fd917..68e90a8953 100644 --- a/Core/HistoryCapture.swift +++ b/Core/HistoryCapture.swift @@ -40,17 +40,16 @@ public class HistoryCapture { public func webViewDidCommit(url: URL) { self.url = url - coordinator.addVisit(of: url.urlOrDuckDuckGoCleanQuery) + coordinator.addVisit(of: url) } public func titleDidChange(_ title: String?, forURL url: URL?) { - guard self.url == url else { + guard let url, self.url == url else { return } - guard let url = url?.urlOrDuckDuckGoCleanQuery, let title, !title.isEmpty else { - return - } + guard let title, !title.isEmpty else { return } + coordinator.updateTitleIfNeeded(title: title, url: url) coordinator.commitChanges(url: url) } diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 472635c170..2f5a6d5e9e 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -100,9 +100,14 @@ extension Pixel { case homeScreenEditFavorite case homeScreenDeleteFavorite - case autocompleteSelectedLocal - case autocompleteSelectedRemote - + case autocompleteEnabled + case autocompleteDisabled + case autocompleteClickPhrase + case autocompleteClickWebsite + case autocompleteClickBookmark + case autocompleteClickFavorite + case autocompleteClickHistory + case feedbackPositive case feedbackNegativePrefix(category: String) @@ -677,9 +682,14 @@ extension Pixel.Event { case .homeScreenEditFavorite: return "mh_ef" case .homeScreenDeleteFavorite: return "mh_df" - case .autocompleteSelectedLocal: return "m_au_l" - case .autocompleteSelectedRemote: return "m_au_r" - + case .autocompleteEnabled: return "m_autocomplete_toggled_on" + case .autocompleteDisabled: return "m_autocomplete_toggled_off" + case .autocompleteClickPhrase: return "m_autocomplete_click_phrase" + case .autocompleteClickWebsite: return "m_autocomplete_click_website" + case .autocompleteClickBookmark: return "m_autocomplete_click_bookmark" + case .autocompleteClickFavorite: return "m_autocomplete_click_favorite" + case .autocompleteClickHistory: return "m_autocomplete_click_history" + case .feedbackPositive: return "mfbs_positive_submit" case .feedbackNegativePrefix(category: let category): return "mfbs_negative_\(category)" @@ -1133,7 +1143,7 @@ extension Pixel.Event { case .historyInsertVisitFailed: return "m_debug_history-insert-visit-failed" case .historyRemoveVisitsFailed: return "m_debug_history-remove-visits-failed" - // MARK: Privacy pro + // MARK: Privacy pro case .privacyProSubscriptionActive: return "m_privacy-pro_app_subscription_active" case .privacyProOfferScreenImpression: return "m_privacy-pro_offer_screen_impression" case .privacyProPurchaseAttempt: return "m_privacy-pro_terms-conditions_subscribe_click" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ef7ff84466..39c918f68a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -373,6 +373,7 @@ 851CD674244D7E6000331B98 /* UserDefaultsExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85449F0023FEAF3000512AAF /* UserDefaultsExtension.swift */; }; 851DFD87212C39D300D95F20 /* TabSwitcherButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851DFD86212C39D300D95F20 /* TabSwitcherButton.swift */; }; 851DFD8A212C5EE800D95F20 /* TabSwitcherButtonTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 851DFD89212C5EE800D95F20 /* TabSwitcherButtonTests.swift */; }; + 851F74262B9A1BFD00747C42 /* Suggestions in Frameworks */ = {isa = PBXBuildFile; productRef = 851F74252B9A1BFD00747C42 /* Suggestions */; }; 85200FA11FBC5BB5001AF290 /* DDGPersistenceContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85200FA01FBC5BB5001AF290 /* DDGPersistenceContainer.swift */; }; 8521FDE6238D414B00A44CC3 /* FileStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8521FDE4238D411400A44CC3 /* FileStoreTests.swift */; }; 8524AAAC2A3888FE00EEC6D2 /* Waitlist.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8524AAAB2A3888FE00EEC6D2 /* Waitlist.xcassets */; }; @@ -424,6 +425,7 @@ 8551912724746EDC0010FDD0 /* SnapshotHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */; }; 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */; }; 855D914D2063EF6A00C4B448 /* TabSwitcherTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */; }; + 8562CE152B9B645C00E1D399 /* CachedBookmarkSuggestions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8562CE142B9B645C00E1D399 /* CachedBookmarkSuggestions.swift */; }; 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */; }; 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */; }; 8565A34D1FC8DFE400239327 /* LaunchTabNotificationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */; }; @@ -500,7 +502,6 @@ 85DFEDF724CB1CAB00973FE7 /* ShareSheet.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85DFEDF624CB1CAB00973FE7 /* ShareSheet.xcassets */; }; 85DFEDF924CF3D0E00973FE7 /* TabsBarCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85DFEDF824CF3D0E00973FE7 /* TabsBarCell.swift */; }; 85E242172AB1B54D000F3E28 /* ReturnUserMeasurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E242162AB1B54D000F3E28 /* ReturnUserMeasurement.swift */; }; - 85E5603026541D9E00F4DC44 /* AutocompleteRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E5602E26541D1D00F4DC44 /* AutocompleteRequestTests.swift */; }; 85E58C2C28FDA94F006A801A /* FavoritesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85E58C2B28FDA94F006A801A /* FavoritesViewController.swift */; }; 85EE7F55224667DD000FE757 /* WebContainer.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85EE7F54224667DD000FE757 /* WebContainer.storyboard */; }; 85EE7F572246685B000FE757 /* WebContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85EE7F562246685B000FE757 /* WebContainerViewController.swift */; }; @@ -589,7 +590,6 @@ 988AC355257E47C100793C64 /* RequeryLogic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988AC354257E47C100793C64 /* RequeryLogic.swift */; }; 988F3DCF237D5C0F00AEE34C /* SchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988F3DCE237D5C0F00AEE34C /* SchemeHandler.swift */; }; 988F3DD3237DE8D900AEE34C /* ForgetDataAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 988F3DD2237DE8D900AEE34C /* ForgetDataAlert.swift */; }; - 98982B3422F8D8E400578AC9 /* Debounce.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98982B3322F8D8E400578AC9 /* Debounce.swift */; }; 98983096255B5019003339A2 /* BookmarksCachingSearchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98983095255B5019003339A2 /* BookmarksCachingSearchTests.swift */; }; 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98999D5822FDA41500CBBE1B /* BasicAuthenticationAlert.swift */; }; 989B337522D7EF2100437824 /* EmptyCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 989B337422D7EF2100437824 /* EmptyCollectionReusableView.swift */; }; @@ -929,7 +929,6 @@ F176699F1E40BC86003D3222 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F176699D1E40BC86003D3222 /* Settings.storyboard */; }; F17669D71E43401C003D3222 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17669D61E43401C003D3222 /* MainViewController.swift */; }; F17843E91F36226700390DCD /* MockFiles in Resources */ = {isa = PBXBuildFile; fileRef = F17843E81F36226700390DCD /* MockFiles */; }; - F17922DB1E717C8D006E3D97 /* Suggestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17922DA1E717C8D006E3D97 /* Suggestion.swift */; }; F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17922DD1E7192E6006E3D97 /* SuggestionTableViewCell.swift */; }; F17922E01E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17922DF1E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift */; }; F17922E21E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17922E11E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift */; }; @@ -941,7 +940,6 @@ F194FAFB1F14E622009B4DF8 /* UIFontExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F194FAFA1F14E622009B4DF8 /* UIFontExtensionTests.swift */; }; F198D78E1E39762C0088DA8A /* StringExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D78D1E39762C0088DA8A /* StringExtensionTests.swift */; }; F198D7981E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */; }; - F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A568391E70F98E0081082E /* AutocompleteRequest.swift */; }; F1A886781F29394E0096251E /* WebCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1A886771F29394E0096251E /* WebCacheManager.swift */; }; F1AE54E81F0425FC00D9A700 /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */; }; F1BE54581E69DE1000FCF649 /* TutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1BE54571E69DE1000FCF649 /* TutorialSettings.swift */; }; @@ -1538,6 +1536,7 @@ 8551912624746EDC0010FDD0 /* SnapshotHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SnapshotHelper.swift; path = fastlane/SnapshotHelper.swift; sourceTree = SOURCE_ROOT; }; 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+PDFRendering.swift"; sourceTree = ""; }; 855D914C2063EF6A00C4B448 /* TabSwitcherTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSwitcherTransition.swift; sourceTree = ""; }; + 8562CE142B9B645C00E1D399 /* CachedBookmarkSuggestions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedBookmarkSuggestions.swift; sourceTree = ""; }; 8563A03B1F9288D600F04442 /* BrowserChromeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrowserChromeManager.swift; sourceTree = ""; }; 8565A34A1FC8D96B00239327 /* LaunchTabNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotification.swift; sourceTree = ""; }; 8565A34C1FC8DFE400239327 /* LaunchTabNotificationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchTabNotificationTests.swift; sourceTree = ""; }; @@ -1612,7 +1611,6 @@ 85DFEDF624CB1CAB00973FE7 /* ShareSheet.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = ShareSheet.xcassets; sourceTree = ""; }; 85DFEDF824CF3D0E00973FE7 /* TabsBarCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsBarCell.swift; sourceTree = ""; }; 85E242162AB1B54D000F3E28 /* ReturnUserMeasurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReturnUserMeasurement.swift; sourceTree = ""; }; - 85E5602E26541D1D00F4DC44 /* AutocompleteRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutocompleteRequestTests.swift; sourceTree = ""; }; 85E58C2B28FDA94F006A801A /* FavoritesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesViewController.swift; sourceTree = ""; }; 85EE7F54224667DD000FE757 /* WebContainer.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = WebContainer.storyboard; sourceTree = ""; }; 85EE7F562246685B000FE757 /* WebContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebContainerViewController.swift; sourceTree = ""; }; @@ -2201,7 +2199,6 @@ 988F3DCE237D5C0F00AEE34C /* SchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchemeHandler.swift; sourceTree = ""; }; 988F3DD2237DE8D900AEE34C /* ForgetDataAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForgetDataAlert.swift; sourceTree = ""; }; 9896632322C56716007BE4FE /* EtagStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EtagStorage.swift; sourceTree = ""; }; - 98982B3322F8D8E400578AC9 /* Debounce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Debounce.swift; sourceTree = ""; }; 98983095255B5019003339A2 /* BookmarksCachingSearchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksCachingSearchTests.swift; sourceTree = ""; }; 98987E6E251EAC3B006F75CD /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; }; 98987E70251EAC3B006F75CD /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2626,7 +2623,6 @@ F176699E1E40BC86003D3222 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Settings.storyboard; sourceTree = ""; }; F17669D61E43401C003D3222 /* MainViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; F17843E81F36226700390DCD /* MockFiles */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MockFiles; sourceTree = ""; }; - F17922DA1E717C8D006E3D97 /* Suggestion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Suggestion.swift; sourceTree = ""; }; F17922DD1E7192E6006E3D97 /* SuggestionTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SuggestionTableViewCell.swift; sourceTree = ""; }; F17922DF1E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteViewControllerDelegate.swift; sourceTree = ""; }; F17922E11E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoSuggestionsTableViewCell.swift; sourceTree = ""; }; @@ -2639,7 +2635,6 @@ F197EA3B1E6885F20029BDC1 /* TextFieldWithInsets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TextFieldWithInsets.swift; path = ../Core/TextFieldWithInsets.swift; sourceTree = ""; }; F198D78D1E39762C0088DA8A /* StringExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringExtensionTests.swift; sourceTree = ""; }; F198D7971E3A45D90088DA8A /* WKWebViewConfigurationExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WKWebViewConfigurationExtensionTests.swift; sourceTree = ""; }; - F1A568391E70F98E0081082E /* AutocompleteRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutocompleteRequest.swift; sourceTree = ""; }; F1A886771F29394E0096251E /* WebCacheManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WebCacheManager.swift; sourceTree = ""; }; F1AA54601E48D90700223211 /* NotificationCenter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NotificationCenter.framework; path = System/Library/Frameworks/NotificationCenter.framework; sourceTree = SDKROOT; }; F1AE54E71F0425FC00D9A700 /* AuthenticationViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = ""; }; @@ -2815,6 +2810,7 @@ EE8E568A2A56BCE400F11DCA /* NetworkProtection in Frameworks */, CBC83E3429B631780008E19C /* Configuration in Frameworks */, D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */, + 851F74262B9A1BFD00747C42 /* Suggestions in Frameworks */, 98A16C2D28A11D6200A6C003 /* BrowserServicesKit in Frameworks */, 8599690F29D2F1C100DBF9FA /* DDGSync in Frameworks */, 851481882A600EFC00ABC65F /* RemoteMessaging in Frameworks */, @@ -4122,14 +4118,6 @@ name = iPad; sourceTree = ""; }; - 85E5602D26541D0900F4DC44 /* AutoComplete */ = { - isa = PBXGroup; - children = ( - 85E5602E26541D1D00F4DC44 /* AutocompleteRequestTests.swift */, - ); - name = AutoComplete; - sourceTree = ""; - }; 85EE7F53224667C3000FE757 /* WebContainer */ = { isa = PBXGroup; children = ( @@ -4999,7 +4987,6 @@ F17669A21E411D63003D3222 /* Application */, 026F08B629B7DC130079B9DF /* AppTrackingProtection */, 981FED7222045FFA008488D7 /* AutoClear */, - 85E5602D26541D0900F4DC44 /* AutoComplete */, 1E1D8B5B2994FF7800C96994 /* Autoconsent */, F40F843228C92B1C0081AE75 /* Autofill */, 98559FD0267099F400A83094 /* ContentBlocker */, @@ -5172,7 +5159,6 @@ F143C3251E4A9A0E00CFDE3A /* URLExtension.swift */, 1E4DCF4B27B6A4CB00961E25 /* URLFileExtension.swift */, F1075C911E9EF827006BE8A8 /* UserDefaultsExtension.swift */, - 98982B3322F8D8E400578AC9 /* Debounce.swift */, 1CB7B82023CEA1F800AA24EA /* DateExtension.swift */, 1E8AD1DA27C51AE000ABA377 /* TimeIntervalExtension.swift */, 85449EFA23FDA0BC00512AAF /* UserDefaultsPropertyWrapper.swift */, @@ -5205,8 +5191,6 @@ F15D43211E70849A00BF2CDC /* Autocomplete */ = { isa = PBXGroup; children = ( - F17922D31E7109C4006E3D97 /* API */, - F17922DC1E717C91006E3D97 /* Domain */, F17922D41E7109DB006E3D97 /* UI */, ); name = Autocomplete; @@ -5259,14 +5243,6 @@ name = Mocks; sourceTree = ""; }; - F17922D31E7109C4006E3D97 /* API */ = { - isa = PBXGroup; - children = ( - F1A568391E70F98E0081082E /* AutocompleteRequest.swift */, - ); - name = API; - sourceTree = ""; - }; F17922D41E7109DB006E3D97 /* UI */ = { isa = PBXGroup; children = ( @@ -5275,18 +5251,11 @@ F17922DF1E71BB59006E3D97 /* AutocompleteViewControllerDelegate.swift */, F17922E11E71CD67006E3D97 /* NoSuggestionsTableViewCell.swift */, F17922DD1E7192E6006E3D97 /* SuggestionTableViewCell.swift */, + 8562CE142B9B645C00E1D399 /* CachedBookmarkSuggestions.swift */, ); name = UI; sourceTree = ""; }; - F17922DC1E717C91006E3D97 /* Domain */ = { - isa = PBXGroup; - children = ( - F17922DA1E717C8D006E3D97 /* Suggestion.swift */, - ); - name = Domain; - sourceTree = ""; - }; F17D722C1E8B3563003E8B0E /* Domain */ = { isa = PBXGroup; children = ( @@ -5961,6 +5930,7 @@ D61CDA152B7CF77300A0FBB9 /* Subscription */, D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */, 858D009C2B9799FC004E5B4C /* History */, + 851F74252B9A1BFD00747C42 /* Suggestions */, ); productName = Core; productReference = F143C2E41E4A4CD400CFDE3A /* Core.framework */; @@ -6842,7 +6812,6 @@ 0268FC132A449F04000EE6A2 /* OnboardingContainerView.swift in Sources */, 858650D9246B0D3C00C36F8A /* DaxOnboardingViewController.swift in Sources */, 312E5746283BB04A00C18FA0 /* AutofillEmptySearchView.swift in Sources */, - F1A5683A1E70F98E0081082E /* AutocompleteRequest.swift in Sources */, 8565A34B1FC8D96B00239327 /* LaunchTabNotification.swift in Sources */, 0290472829E861BE0008FE3C /* AppTPTrackerDetailViewModel.swift in Sources */, 311BD1AD2836BB3900AEF6C1 /* AutofillItemsEmptyView.swift in Sources */, @@ -6908,8 +6877,8 @@ 851624C72B96389D002D5CD7 /* HistoryDebugViewController.swift in Sources */, 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */, 85DFEDF924CF3D0E00973FE7 /* TabsBarCell.swift in Sources */, - F17922DB1E717C8D006E3D97 /* Suggestion.swift in Sources */, 020108A729A6ABF600644F9D /* AppTPToggleView.swift in Sources */, + 8562CE152B9B645C00E1D399 /* CachedBookmarkSuggestions.swift in Sources */, 02A54A982A093126000C8FED /* AppTPHomeViewModel.swift in Sources */, C13F3F682B7F88100083BE40 /* AuthConfirmationPromptView.swift in Sources */, F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */, @@ -7051,7 +7020,6 @@ 31C138B227A4097800FFD4B2 /* DownloadTestsHelper.swift in Sources */, 1E1D8B5D2994FFE100C96994 /* AutoconsentMessageProtocolTests.swift in Sources */, 85C11E532090B23A00BFFEB4 /* UserDefaultsHomeRowReminderStorageTests.swift in Sources */, - 85E5603026541D9E00F4DC44 /* AutocompleteRequestTests.swift in Sources */, F1DA2F7D1EBCF23700313F51 /* ExternalUrlSchemeTests.swift in Sources */, F198D78E1E39762C0088DA8A /* StringExtensionTests.swift in Sources */, 31B1FA87286EFC5C00CA3C1C /* XCTestCaseExtension.swift in Sources */, @@ -7247,7 +7215,6 @@ 37E615752A5F533E00ACD63D /* SyncCredentialsAdapter.swift in Sources */, 02CA904B24F6C11A00D41DDF /* NavigatorSharePatchUserScript.swift in Sources */, 85BDC3192436161C0053DB07 /* LoginFormDetectionUserScript.swift in Sources */, - 98982B3422F8D8E400578AC9 /* Debounce.swift in Sources */, 37DF000A29F9C416002B7D3E /* SyncMetadataDatabase.swift in Sources */, F143C3291E4A9A0E00CFDE3A /* URLExtension.swift in Sources */, F143C3271E4A9A0E00CFDE3A /* Logging.swift in Sources */, @@ -10028,7 +9995,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 125.0.2; + version = 126.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10186,6 +10153,11 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = RemoteMessaging; }; + 851F74252B9A1BFD00747C42 /* Suggestions */ = { + isa = XCSwiftPackageProductDependency; + package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Suggestions; + }; 85875B6029912A9900115F05 /* SyncUI */ = { isa = XCSwiftPackageProductDependency; productName = SyncUI; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f2336cc993..beb5810bd7 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" : "810bf41347ff437b5d0154405a238553537240a4", - "version" : "125.0.2" + "revision" : "7656e94efcf4eedf1c16152c63f57fb52b6ad079", + "version" : "126.0.0" } }, { diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme index e564636928..d3926becad 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme @@ -124,7 +124,7 @@ + isEnabled = "YES"> Void - - private let url: URL - private var task: URLSessionDataTask? - - init(query: String) throws { - self.url = try URL.makeAutocompleteURL(for: query) - } - - func execute(completion: @escaping Completion) { - var request = URLRequest.developerInitiated(url) - request.allHTTPHeaderFields = APIRequest.Headers().httpHeaders - - task = AutocompleteRequest.session.dataTask(with: request) { [weak self] (data, _, error) in - guard let weakSelf = self else { return } - do { - let suggestions = try weakSelf.processResult(data: data, error: error) - weakSelf.complete(completion, withSuccess: suggestions) - } catch { - weakSelf.complete(completion, withError: error) - } - } - task?.resume() - } - - private func processResult(data: Data?, error: Swift.Error?) throws -> [Suggestion] { - if let error = error { throw error } - guard let data = data else { throw Error.noData } - let entries = try JSONDecoder().decode([AutocompleteEntry].self, from: data) - - return entries.compactMap { - guard let phrase = $0.phrase else { return nil } - - if let isNav = $0.isNav { - // We definitely have a nav indication so use it. Phrase should be a fully qualified URL. - // Assume HTTP and that we'll auto-upgrade if needed. - let url = isNav ? URL(string: "http://\(phrase)") : nil - return Suggestion(source: .remote, suggestion: phrase, url: url) - } else { - // We need to infer nav based on the phrase to maintain previous behaviour (ie treat phrase that look like URLs like URLs) - let url = URL.webUrl(from: phrase) - return Suggestion(source: .remote, suggestion: phrase, url: url) - } - } - } - - private func complete(_ completion: @escaping Completion, withSuccess suggestions: [Suggestion]) { - DispatchQueue.main.async { - completion(suggestions, nil) - } - } - - private func complete(_ completion: @escaping Completion, withError error: Swift.Error) { - DispatchQueue.main.async { - completion(nil, error) - } - } - - func cancel() { - task?.cancel() - } -} diff --git a/DuckDuckGo/AutocompleteViewController.swift b/DuckDuckGo/AutocompleteViewController.swift index caa9e5b35a..8f57da2bb2 100644 --- a/DuckDuckGo/AutocompleteViewController.swift +++ b/DuckDuckGo/AutocompleteViewController.swift @@ -21,29 +21,42 @@ import Common import UIKit import Core import DesignResourcesKit +import Suggestions +import Networking +import CoreData +import Persistence +import History +import Combine class AutocompleteViewController: UIViewController { + private static let session = URLSession(configuration: .ephemeral) + struct Constants { - static let debounceDelay: TimeInterval = 0.1 + static let debounceDelay = 100 // millis static let minItems = 1 - static let maxLocalItems = 2 } weak var delegate: AutocompleteViewControllerDelegate? weak var presentationDelegate: AutocompleteViewControllerPresentationDelegate? - private var lastRequest: AutocompleteRequest? + private var task: URLSessionDataTask? + private var loader: SuggestionLoading? private var receivedResponse = false private var pendingRequest = false - fileprivate var query = "" + @Published fileprivate var query = "" + fileprivate var queryDebounceCancellable: AnyCancellable? + fileprivate var suggestions = [Suggestion]() fileprivate var selectedItem = -1 - private var bookmarksSearch: BookmarksStringSearch! - + private var historyCoordinator: HistoryCoordinating! + private var bookmarksDatabase: CoreDataDatabase! private var appSettings: AppSettings! + private lazy var cachedBookmarks: CachedBookmarks = { + CachedBookmarks(bookmarksDatabase) + }() var backgroundColor: UIColor { appSettings.currentAddressBarPosition.isBottom ? @@ -63,20 +76,19 @@ class AutocompleteViewController: UIViewController { } private var hidesBarsOnSwipeDefault = true - - private let debounce = Debounce(queue: .main, seconds: Constants.debounceDelay) @IBOutlet weak var tableView: UITableView! var shouldOffsetY = false - static func loadFromStoryboard(bookmarksSearch: BookmarksStringSearch, + static func loadFromStoryboard(bookmarksDatabase: CoreDataDatabase, + historyCoordinator: HistoryCoordinating, appSettings: AppSettings = AppDependencyProvider.shared.appSettings) -> AutocompleteViewController { let storyboard = UIStoryboard(name: "Autocomplete", bundle: nil) - guard let controller = storyboard.instantiateInitialViewController() as? AutocompleteViewController else { fatalError("Failed to instatiate correct Autocomplete view controller") } - controller.bookmarksSearch = bookmarksSearch + controller.bookmarksDatabase = bookmarksDatabase + controller.historyCoordinator = historyCoordinator controller.appSettings = appSettings return controller } @@ -85,6 +97,12 @@ class AutocompleteViewController: UIViewController { super.viewDidLoad() configureTableView() applyTheme(ThemeManager.shared.currentTheme) + + queryDebounceCancellable = $query + .debounce(for: .milliseconds(Constants.debounceDelay), scheduler: RunLoop.main) + .sink { [weak self] query in + self?.requestSuggestions(query: query) + } } private func configureTableView() { @@ -125,24 +143,29 @@ class AutocompleteViewController: UIViewController { } func updateQuery(query: String) { - self.query = query selectedItem = -1 cancelInFlightRequests() - debounce.schedule { [weak self] in - self?.requestSuggestions(query: query) - } + self.query = query } func willDismiss(with query: String) { - guard selectedItem != -1, selectedItem < suggestions.count else { return } - + guard suggestions.indices.contains(selectedItem) else { return } let suggestion = suggestions[selectedItem] - if let url = suggestion.url { - if query == url.absoluteString { - firePixel(selectedSuggestion: suggestion) - } - } else if query == suggestion.suggestion { - firePixel(selectedSuggestion: suggestion) + firePixelForSelectedSuggestion(suggestion) + } + + private func firePixelForSelectedSuggestion(_ suggestion: Suggestion) { + switch suggestion { + case .phrase: + Pixel.fire(pixel: .autocompleteClickPhrase) + case .website: + Pixel.fire(pixel: .autocompleteClickWebsite) + case .bookmark(_, _, isFavorite: let isFavorite, _): + Pixel.fire(pixel: isFavorite ? .autocompleteClickFavorite : .autocompleteClickBookmark) + case .historyEntry: + Pixel.fire(pixel: .autocompleteClickHistory) + case .unknown(value: let value): + assertionFailure("Unknown suggestion \(value)") } } @@ -152,47 +175,32 @@ class AutocompleteViewController: UIViewController { } private func cancelInFlightRequests() { - if let inFlightRequest = lastRequest { - inFlightRequest.cancel() - lastRequest = nil - } + task?.cancel() + task = nil } private func requestSuggestions(query: String) { selectedItem = -1 tableView.reloadData() - do { - lastRequest = try AutocompleteRequest(query: query) - pendingRequest = true - } catch { - os_log("Couldn‘t form AutocompleteRequest for query “%s”: %s", log: .lifecycleLog, type: .debug, query, error.localizedDescription) - lastRequest = nil - pendingRequest = false - return - } - lastRequest!.execute { [weak self] (suggestions, error) in - guard let strongSelf = self else { return } - - Task { @MainActor in - let matches = strongSelf.bookmarksSearch.search(query: query) - let notQueryMatches = matches.filter { $0.url.absoluteString != query } - let filteredMatches = notQueryMatches.prefix(Constants.maxLocalItems) - let localSuggestions = filteredMatches.map { Suggestion(source: .local, - suggestion: $0.title, - url: $0.url) - } - - guard let suggestions = suggestions, error == nil else { - os_log("%s", log: .generalLog, type: .debug, error?.localizedDescription ?? "Failed to retrieve suggestions") - self?.updateSuggestions(localSuggestions) - return - } - - let combinedSuggestions = localSuggestions + suggestions - strongSelf.updateSuggestions(Array(combinedSuggestions)) - strongSelf.pendingRequest = false + loader = SuggestionLoader(dataSource: self, urlFactory: { phrase in + guard let url = URL(trimmedAddressBarString: phrase), + let scheme = url.scheme, + scheme.description.hasPrefix("http"), + url.isValid else { + return nil + } + + return url + }) + pendingRequest = true + + loader?.getSuggestions(query: query) { [weak self] result, error in + defer { + self?.pendingRequest = false } + guard error == nil else { return } + self?.updateSuggestions(result?.all ?? []) } } @@ -262,37 +270,29 @@ extension AutocompleteViewController: UITableViewDataSource { if appSettings.currentAddressBarPosition.isBottom && suggestions.isEmpty { return view.frame.height } - return 46 + + let defaultHeight: CGFloat = 46 + guard suggestions.indices.contains(indexPath.row) else { return defaultHeight } + + switch suggestions[indexPath.row] { + case .bookmark, .historyEntry: + return 60 + default: + return defaultHeight + } } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return receivedResponse ? max(Constants.minItems, suggestions.count) : 0 } - - private func firePixel(selectedSuggestion: Suggestion) { - let resultsIncludeBookmarks: Bool - if let firstSuggestion = suggestions.first { - resultsIncludeBookmarks = firstSuggestion.source == .local - } else { - resultsIncludeBookmarks = false - } - - let params = [PixelParameters.autocompleteBookmarkCapable: bookmarksSearch.hasData ? "true" : "false", - PixelParameters.autocompleteIncludedLocalResults: resultsIncludeBookmarks ? "true" : "false"] - - if selectedSuggestion.source == .local { - Pixel.fire(pixel: .autocompleteSelectedLocal, withAdditionalParameters: params) - } else { - Pixel.fire(pixel: .autocompleteSelectedRemote, withAdditionalParameters: params) - } - } + } extension AutocompleteViewController: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let suggestion = suggestions[indexPath.row] - firePixel(selectedSuggestion: suggestion) delegate?.autocomplete(selectedSuggestion: suggestion) + firePixelForSelectedSuggestion(suggestion) } } @@ -334,3 +334,37 @@ extension AutocompleteViewController { } } + +extension AutocompleteViewController: SuggestionLoadingDataSource { + + func history(for suggestionLoading: Suggestions.SuggestionLoading) -> [HistorySuggestion] { + return historyCoordinator.history ?? [] + } + + func bookmarks(for suggestionLoading: Suggestions.SuggestionLoading) -> [Suggestions.Bookmark] { + return cachedBookmarks.all + } + + func suggestionLoading(_ suggestionLoading: Suggestions.SuggestionLoading, suggestionDataFromUrl url: URL, withParameters parameters: [String: String], completion: @escaping (Data?, Error?) -> Void) { + var queryURL = url + parameters.forEach { + queryURL = queryURL.appendingParameter(name: $0.key, value: $0.value) + } + + var request = URLRequest.developerInitiated(queryURL) + request.allHTTPHeaderFields = APIRequest.Headers().httpHeaders + task = Self.session.dataTask(with: request) { data, _, error in + completion(data, error) + } + task?.resume() + } + +} + +extension HistoryEntry: HistorySuggestion { + + public var numberOfVisits: Int { + return numberOfTotalVisits + } + +} diff --git a/DuckDuckGo/AutocompleteViewControllerDelegate.swift b/DuckDuckGo/AutocompleteViewControllerDelegate.swift index fbb0b6d9fb..e7f3671328 100644 --- a/DuckDuckGo/AutocompleteViewControllerDelegate.swift +++ b/DuckDuckGo/AutocompleteViewControllerDelegate.swift @@ -18,6 +18,7 @@ // import UIKit +import Suggestions protocol AutocompleteViewControllerDelegate: AnyObject { diff --git a/DuckDuckGo/Base.lproj/Autocomplete.storyboard b/DuckDuckGo/Base.lproj/Autocomplete.storyboard index 3254452129..2e7d24e4c4 100644 --- a/DuckDuckGo/Base.lproj/Autocomplete.storyboard +++ b/DuckDuckGo/Base.lproj/Autocomplete.storyboard @@ -1,9 +1,10 @@ - + - + + @@ -20,7 +21,7 @@ - + @@ -34,13 +35,28 @@ + + + + - + + + + + + + @@ -959,13 +968,13 @@ - + - + diff --git a/DuckDuckGo/EmailSignupViewController.swift b/DuckDuckGo/EmailSignupViewController.swift index cf8eca1863..38890335c7 100644 --- a/DuckDuckGo/EmailSignupViewController.swift +++ b/DuckDuckGo/EmailSignupViewController.swift @@ -425,7 +425,16 @@ extension EmailSignupViewController: SecureVaultManagerDelegate { } func secureVaultManager(_: SecureVaultManager, didRequestRuntimeConfigurationForDomain domain: String, completionHandler: @escaping (String?) -> Void) { - completionHandler(nil) + let contentScopeProperties = ContentScopeProperties(gpcEnabled: AppDependencyProvider.shared.appSettings.sendDoNotSell, + sessionKey: "", + featureToggles: ContentScopeFeatureToggles.supportedFeaturesOniOS) + + let runtimeConfig = DefaultAutofillSourceProvider.Builder(privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + properties: contentScopeProperties) + .build() + .buildRuntimeConfigResponse() + + completionHandler(runtimeConfig) } func secureVaultManager(_: SecureVaultManager, didReceivePixel pixel: AutofillUserScript.JSPixel) { From 9f2f4b823b844bf8a60ffed7a6ae55e07c9b8b1e Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 22 Mar 2024 07:52:32 -0700 Subject: [PATCH 150/245] Update VPN configuration name (#2623) Task/Issue URL: https://app.asana.com/0/414235014887631/1206904075625849/f Tech Design URL: CC: Description: This PR updates the VPN configuration name. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- DuckDuckGo/NetworkProtectionTunnelController.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0451ab8d4a..a193190ebd 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.0; + version = 129.1.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d57c1ee698..646823f365 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" : "2f64ae6ce5aee5d648258eef5a5b39e61b9bd8e3", - "version" : "129.1.0" + "revision" : "40093418106389c945bbe6df585fdb02833716df", + "version" : "129.1.1" } }, { diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index e8aea07fce..1d130bf0cc 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -222,7 +222,7 @@ final class NetworkProtectionTunnelController: TunnelController { /// Setups the tunnel manager if it's not set up already. /// private func setup(_ tunnelManager: NETunnelProviderManager) { - tunnelManager.localizedDescription = "DuckDuckGo Network Protection" + tunnelManager.localizedDescription = "DuckDuckGo VPN" tunnelManager.isEnabled = true tunnelManager.protocolConfiguration = { From f2e2c3175a0531b435028895f927a45adbf83561 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Fri, 22 Mar 2024 10:46:40 -0500 Subject: [PATCH 151/245] Fix webview leak in performancemetrics (#2624) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a193190ebd..0bca2809f6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.1; + version = 129.1.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 646823f365..40fa1c998b 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" : "40093418106389c945bbe6df585fdb02833716df", - "version" : "129.1.1" + "revision" : "781b5f10b6030273e745ba3c0b71ff9317d8ade0", + "version" : "129.1.2" } }, { From 40fbb495ad212954a3f6da3db8201443541ed5a1 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Fri, 22 Mar 2024 21:05:17 +0100 Subject: [PATCH 152/245] Rework the caching logic for subscription and entitlements (#2627) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- DuckDuckGo/AppDelegate.swift | 19 ++++++++++--------- .../SubscriptionDebugViewController.swift | 4 ++-- ...etworkProtectionPacketTunnelProvider.swift | 2 +- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0bca2809f6..bbf3a43ffc 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.2; + version = 129.1.3; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 40fa1c998b..2078795732 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" : "781b5f10b6030273e745ba3c0b71ff9317d8ade0", - "version" : "129.1.2" + "revision" : "91d9eb23c33ae8c1a6e19314c0349e42eab70326", + "version" : "129.1.3" } }, { @@ -183,7 +183,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 774472e543..1c141e9ae1 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -435,7 +435,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } #endif SubscriptionPurchaseEnvironment.current = .appStore - await AccountManager().checkSubscriptionState() } } #endif @@ -508,18 +507,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func updateSubscriptionStatus() { #if SUBSCRIPTION Task { - guard let token = AccountManager().accessToken else { - return - } - let result = await SubscriptionService.getSubscription(accessToken: token) + let accountManager = AccountManager() - 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) + } else { + accountManager.signOut() } - case .failure: break } + + _ = await accountManager.fetchEntitlements(cachePolicy: .reloadIgnoringLocalCacheData) } #endif } diff --git a/DuckDuckGo/SubscriptionDebugViewController.swift b/DuckDuckGo/SubscriptionDebugViewController.swift index 429420d8d0..8368fb92e2 100644 --- a/DuckDuckGo/SubscriptionDebugViewController.swift +++ b/DuckDuckGo/SubscriptionDebugViewController.swift @@ -222,7 +222,7 @@ final class SubscriptionDebugViewController: UITableViewController { showAlert(title: "Not authenticated", message: "No authenticated user found! - Subscription not available") return } - switch await SubscriptionService.getSubscription(accessToken: token) { + switch await SubscriptionService.getSubscription(accessToken: token, cachePolicy: .reloadIgnoringLocalCacheData) { case .success(let response): showAlert(title: "Subscription info", message: "\(response)") case .failure(let error): @@ -240,7 +240,7 @@ final class SubscriptionDebugViewController: UITableViewController { } let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] for entitlement in entitlements { - if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { + if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement, cachePolicy: .reloadIgnoringLocalCacheData) { let resultSummary = "Entitlement check for \(entitlement.rawValue): \(result)" results.append(resultSummary) print(resultSummary) diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index d2205689f2..a2d7eb5989 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -331,7 +331,7 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { } let result = await AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) - .hasEntitlement(for: .networkProtection, cachePolicy: .reloadIgnoringLocalCacheData) + .hasEntitlement(for: .networkProtection) switch result { case .success(let hasEntitlement): return .success(hasEntitlement) From 07e7aa7c95287cc120abfdaf82342ec2fcc43f64 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Fri, 22 Mar 2024 17:05:24 -0400 Subject: [PATCH 153/245] Address Ship Review issues with entitlement expiration (#2628) Task/Issue URL: https://app.asana.com/0/1203137811378537/1206896148261286/f Tech Design URL: CC: Description: This PR addresses some Ship Review issues with entitlement expiration not being picked up by adding entitlementsDidChange and accountDidSignOut notification handling Steps to test this PR: Subscribe to PP Remove sub from device VPN should stop and be removed from Settings.app Revoke the entitlements Open another app and go back to the app The app should detect the entitlement change and show messaging & stop the VPN --- DuckDuckGo/MainViewController.swift | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 3f6e04dcb8..3544f62240 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1355,6 +1355,18 @@ class MainViewController: UIViewController { self?.onNetworkProtectionAccountSignIn(notification) } .store(in: &vpnCancellables) + NotificationCenter.default.publisher(for: .entitlementsDidChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + self?.onEntitlementsChange(notification) + } + .store(in: &vpnCancellables) + NotificationCenter.default.publisher(for: .accountDidSignOut) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + self?.onNetworkProtectionAccountSignOut(notification) + } + .store(in: &vpnCancellables) NotificationCenter.default.publisher(for: .vpnEntitlementMessagingDidChange) .receive(on: DispatchQueue.main) @@ -1409,6 +1421,27 @@ class MainViewController: UIViewController { tunnelDefaults.resetEntitlementMessaging() os_log("[NetP Subscription] Reset expired entitlement messaging", log: .networkProtection, type: .info) } + + @objc + private func onEntitlementsChange(_ notification: Notification) { + Task { + guard case .success(false) = await AccountManager().hasEntitlement(for: .networkProtection) else { return } + + tunnelDefaults.enableEntitlementMessaging() + + let controller = NetworkProtectionTunnelController() + await controller.stop() + } + } + + @objc + private func onNetworkProtectionAccountSignOut(_ notification: Notification) { + Task { + let controller = NetworkProtectionTunnelController() + await controller.stop() + await controller.removeVPN() + } + } #endif @objc From 195ab82d424bfb987677323a73da8f183ae478bb Mon Sep 17 00:00:00 2001 From: Juan Manuel Pereira Date: Fri, 22 Mar 2024 18:33:08 -0300 Subject: [PATCH 154/245] Bump BSK to 129.1.4 (#2629) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bbf3a43ffc..21bcdd74e8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.3; + version = 129.1.4; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2078795732..0d68ef32b2 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" : "91d9eb23c33ae8c1a6e19314c0349e42eab70326", - "version" : "129.1.3" + "revision" : "91b012d9450af211f7c47b9fde811e3a8292b16b", + "version" : "129.1.4" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "edd96481d49b094c260f9a79e078abdbdd3a83fb", - "version" : "5.6.0" + "revision" : "2f44185cca2edefbae7557393a61a23c282abbf8", + "version" : "5.7.0" } }, { From 3d8666d029bd923e0501c2d7d8231989dd1c122e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 22 Mar 2024 23:58:59 +0100 Subject: [PATCH 155/245] Bump submodules/privacy-reference-tests from `6b7ad1e` to `a603ff9` (#2589) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- submodules/privacy-reference-tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/submodules/privacy-reference-tests b/submodules/privacy-reference-tests index 6b7ad1e7f1..a603ff9af2 160000 --- a/submodules/privacy-reference-tests +++ b/submodules/privacy-reference-tests @@ -1 +1 @@ -Subproject commit 6b7ad1e7f15270f9dfeb58a272199f4d57c3eb22 +Subproject commit a603ff9af22ca3ff7ce2e7ffbfe18c447d9f23e8 From e8479dd2184963fc9bdcb1ea6b7b3c960545d724 Mon Sep 17 00:00:00 2001 From: Kate Ferrandino <94081185+KateFerrandino@users.noreply.github.com> Date: Sun, 24 Mar 2024 14:05:06 -0700 Subject: [PATCH 156/245] Clarified readme "instruments" section (#2607) Task/Issue URL: https://app.asana.com/0/414235014887631/1206915607562737/f documentation change only normalized capitalization and grammar --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3f88970641..4f81944322 100644 --- a/README.md +++ b/README.md @@ -31,12 +31,12 @@ We use [SwifLint](https://github.com/realm/SwiftLint) for enforcing Swift style ### Instruments -We have Custom Instruments tool to help visualize and track events that happen during runtime. +We have a Custom Instruments tool to help visualize and track events that happen during runtime. In order to run it: -1. Build a Debug version and install it on Simulator/Device. -2. Select Instruments target and run it on a Mac. A New instance of Instruments app will be run that has a grayed out icon indicating that it works in debug mode with custom instruments attached. -3. Select 'DDG Trace' template or set up a custom one by importing 'DDG Timeline' instrument from Library. +1. Build a debug version and install it on a simulator or device. +2. Select the Instruments target and run it on a Mac. A new instance of the Instruments app will run. It will have a grayed out icon indicating that it works in debug mode with custom instruments attached. +3. Select the 'DDG Trace' template or set up a custom one by importing the 'DDG Timeline' instrument from Library. 4. Start recording. See [Instruments Developer Help](https://help.apple.com/instruments/developer/mac/current/) for reference how to create custom instruments. From 96ceae1aa1c7df120d161afb990f4003fc12f122 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Mon, 25 Mar 2024 00:57:37 -0400 Subject: [PATCH 157/245] Collect extra metadata (#2622) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../Feedback/VPNMetadataCollector.swift | 71 ++++++++++++++----- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 21bcdd74e8..b76bb7a78c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.4; + version = 129.1.6; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0d68ef32b2..e0a30aaaa8 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" : "91b012d9450af211f7c47b9fde811e3a8292b16b", - "version" : "129.1.4" + "branch" : "anh/pp/add-metadata", + "revision" : "0cba47cf651175806bc151366605f8b19802d3ee" } }, { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index a4103cdd00..b858cf4672 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -24,6 +24,7 @@ import Common import NetworkProtection import NetworkExtension import Network +import Subscription struct VPNMetadata: Encodable { @@ -61,11 +62,27 @@ struct VPNMetadata: Encodable { let selectedServer: String } + struct PrivacyProInfo: Encodable { + // swiftlint:disable nesting + enum Source: String, Encodable { + case `internal` + case waitlist + case other + } + // swiftlint:enable nesting + + let enableSource: Source + let betaParticipant: Bool + let hasToken: Bool + let subscriptionActive: Bool + } + let appInfo: AppInfo let deviceInfo: DeviceInfo let networkInfo: NetworkInfo let vpnState: VPNState let vpnSettingsState: VPNSettingsState + let privacyProInfo: PrivacyProInfo func toPrettyPrintedJSON() -> String? { let encoder = JSONEncoder() @@ -99,15 +116,21 @@ protocol VPNMetadataCollector { final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver + private let accessManager: NetworkProtectionAccessController + private let tokenStore: NetworkProtectionTokenStore private let settings: VPNSettings private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver = ConnectionStatusObserverThroughSession(), serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), + networkProtectionAccessManager: NetworkProtectionAccessController = NetworkProtectionAccessController(), + tokenStore: NetworkProtectionTokenStore = NetworkProtectionKeychainTokenStore(), settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { self.statusObserver = statusObserver self.serverInfoObserver = serverInfoObserver + self.accessManager = networkProtectionAccessManager + self.tokenStore = tokenStore self.settings = settings self.defaults = defaults } @@ -118,13 +141,15 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { let networkInfoMetadata = await collectNetworkInformation() let vpnState = await collectVPNState() let vpnSettingsState = collectVPNSettingsState() + let privacyProInfo = collectPrivacyProInfo() return VPNMetadata( appInfo: appInfoMetadata, deviceInfo: deviceInfoMetadata, networkInfo: networkInfoMetadata, vpnState: vpnState, - vpnSettingsState: vpnSettingsState + vpnSettingsState: vpnSettingsState, + privacyProInfo: privacyProInfo ) } @@ -242,7 +267,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { } func collectVPNSettingsState() -> VPNMetadata.VPNSettingsState { - return .init( + .init( connectOnLoginEnabled: settings.connectOnLogin, includeAllNetworksEnabled: settings.includeAllNetworks, enforceRoutesEnabled: settings.enforceRoutes, @@ -251,25 +276,35 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { selectedServer: settings.selectedServer.stringValue ?? "automatic" ) } -} - -extension Network.NWPath { - /// A description that's safe from a privacy standpoint. - /// - /// Ref: https://app.asana.com/0/0/1206712493935053/1206712516729780/f - /// - var anonymousDescription: String { - var description = "NWPath(" - description += "status: \(status), " - - if #available(iOS 14.2, *), case .unsatisfied = status { - description += "unsatisfiedReason: \(unsatisfiedReason), " + func collectPrivacyProInfo() -> VPNMetadata.PrivacyProInfo { + let accessType = accessManager.networkProtectionAccessType() + var hasToken: Bool { + guard let token = try? tokenStore.fetchToken(), + !token.hasPrefix(NetworkProtectionKeychainTokenStore.authTokenPrefix) else { + return false + } + return true } - description += "availableInterfaces: \(availableInterfaces)" - description += ")" + return .init( + enableSource: .init(from: accessManager.networkProtectionAccessType()), + betaParticipant: accessType == .waitlistJoined, + hasToken: hasToken, + subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken != nil + ) + } +} - return description +extension VPNMetadata.PrivacyProInfo.Source { + init(from accessType: NetworkProtectionAccessType) { + switch accessType { + case .inviteCodeInvited: + self = .internal + case .waitlistInvited: + self = .waitlist + default: + self = .other + } } } From 42c96cc3b3217636bea170113ce01abf5d810245 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Mon, 25 Mar 2024 13:20:52 +0100 Subject: [PATCH 158/245] Send correct platform value for App Store purchase options (#2633) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index b76bb7a78c..de9ac444da 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.6; + version = 129.1.7; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e0a30aaaa8..e7ff49b83f 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" : { - "branch" : "anh/pp/add-metadata", - "revision" : "0cba47cf651175806bc151366605f8b19802d3ee" + "revision" : "c34a4d0298a33c4c7f3b8c0ac4ca249b51e350db", + "version" : "129.1.7" } }, { From 784fe398b0ac53faac328911256325ff6c4bc97b Mon Sep 17 00:00:00 2001 From: amddg44 Date: Mon, 25 Mar 2024 14:44:17 +0100 Subject: [PATCH 159/245] BSK update for Autofill Script performance improvements (#2625) Task/Issue URL: https://app.asana.com/0/414709148257752/1206900948756760/f Tech Design URL: CC: Description: Greatly reduces the amount of data passed into the autofill script to improve performance and resolve occasional out of memory crashes --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index de9ac444da..f7a7364ded 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.7; + version = 129.1.8; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index e7ff49b83f..7148d9a0a1 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" : "c34a4d0298a33c4c7f3b8c0ac4ca249b51e350db", - "version" : "129.1.7" + "revision" : "0509e0cf15f1ff8b0f32ac39c262da131fba7f3e", + "version" : "129.1.8" } }, { From 390bdcd6cef900d809d5a8d2f20be9d4daa18e83 Mon Sep 17 00:00:00 2001 From: Lorenzo Mattei Date: Mon, 25 Mar 2024 14:52:04 +0100 Subject: [PATCH 160/245] Replase glitch.me pages with privacy-test-pages.site (#2632) --- .maestro/release_tests/bookmarks.yaml | 2 +- .maestro/release_tests/browsing.yaml | 2 +- .maestro/release_tests/favorites.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.maestro/release_tests/bookmarks.yaml b/.maestro/release_tests/bookmarks.yaml index 4e0822e12d..d77e81e46f 100644 --- a/.maestro/release_tests/bookmarks.yaml +++ b/.maestro/release_tests/bookmarks.yaml @@ -20,7 +20,7 @@ tags: id: "searchEntry" - tapOn: id: "searchEntry" -- inputText: "https://privacy-test-pages.glitch.me" +- inputText: "https://privacy-test-pages.site" - pressKey: Enter # Manage onboarding diff --git a/.maestro/release_tests/browsing.yaml b/.maestro/release_tests/browsing.yaml index 3be02ab923..f191138307 100644 --- a/.maestro/release_tests/browsing.yaml +++ b/.maestro/release_tests/browsing.yaml @@ -20,7 +20,7 @@ tags: id: "searchEntry" - tapOn: id: "searchEntry" -- inputText: "https://privacy-test-pages.glitch.me" +- inputText: "https://privacy-test-pages.site" - pressKey: Enter - tapOn: optional: true diff --git a/.maestro/release_tests/favorites.yaml b/.maestro/release_tests/favorites.yaml index c3d8ae1b28..cdaa68fd46 100644 --- a/.maestro/release_tests/favorites.yaml +++ b/.maestro/release_tests/favorites.yaml @@ -20,7 +20,7 @@ tags: id: "searchEntry" - tapOn: id: "searchEntry" -- inputText: "https://privacy-test-pages.glitch.me" +- inputText: "https://privacy-test-pages.site" - pressKey: Enter # Manage onboarding From c5550c5fdb8e44091e675eda78c6e490e2c6f3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jacek=20=C5=81yp?= Date: Mon, 25 Mar 2024 15:29:25 +0100 Subject: [PATCH 161/245] Fix incorrect omnibar behavior (#2634) Task/Issue URL: https://app.asana.com/0/414709148257752/1206910834230045/f Description: Fix incorrect omnibar behavior. --- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/OmniBar.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7148d9a0a1..9f67334479 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -183,7 +183,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index 7feb722c3e..aa7a17d573 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -375,7 +375,7 @@ class OmniBar: UIView { } func refreshText(forUrl url: URL?, forceFullURL: Bool = false) { - + guard !textField.isEditing else { return } guard let url = url else { textField.text = nil return From d0ee9c36c5ff8e1099345e28e4717286dfd37c4b Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Mon, 25 Mar 2024 08:09:27 -0700 Subject: [PATCH 162/245] VPN ship review changes (#2630) Task/Issue URL: https://app.asana.com/0/414235014887631/1206914003023279/f Tech Design URL: CC: Description: This PR fixes a number of ship review issues: The option to show the VPN screen after purchase was pushing the VC multiple times - this is now fixed The VPN is now fully removed after entitlements expire The VPN disconnected notification is now only displayed if the VPN is installed This PR also pulls in a crash fix from @miasma13 It also cleans up some waitlist code that is no longer really desired in the app. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +-- DuckDuckGo/AppDelegate+Waitlists.swift | 32 ++----------------- DuckDuckGo/AppDelegate.swift | 17 ++++++---- DuckDuckGo/MainViewController.swift | 8 +++-- .../NetworkProtectionAccessController.swift | 1 - .../NetworkProtectionFeatureVisibility.swift | 13 ++++++-- .../NetworkProtectionTunnelController.swift | 7 ++++ DuckDuckGo/SettingsView.swift | 9 ++++-- ...workProtectionFeatureVisibilityTests.swift | 10 +++--- 10 files changed, 50 insertions(+), 53 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f7a7364ded..2e5f393308 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.1.8; + version = 129.2.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 9f67334479..15e467a17c 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" : "0509e0cf15f1ff8b0f32ac39c262da131fba7f3e", - "version" : "129.1.8" + "revision" : "284c328a097132a12e8abcf94d8f4d369063dcb4", + "version" : "129.2.0" } }, { diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 5d58408534..4f46ee1f3c 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -37,7 +37,7 @@ extension AppDelegate { func checkWaitlists() { #if NETWORK_PROTECTION - if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() { + if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { checkNetworkProtectionWaitlist() } #endif @@ -54,35 +54,7 @@ extension AppDelegate { VPNWaitlist.shared.fetchInviteCodeIfAvailable { [weak self] error in guard error == nil else { - - if error == .alreadyHasInviteCode, UIApplication.shared.applicationState == .active { - // If the user already has an invite code but their auth token has gone missing, attempt to redeem it again. - let tokenStore = NetworkProtectionKeychainTokenStore() - let waitlistStorage = VPNWaitlist.shared.waitlistStorage - let configManager = ContentBlocking.shared.privacyConfigurationManager - let waitlistBetaActive = configManager.privacyConfig.isSubfeatureEnabled(NetworkProtectionSubfeature.waitlistBetaActive) - - if let inviteCode = waitlistStorage.getWaitlistInviteCode(), - !tokenStore.isFeatureActivated, - waitlistBetaActive { - let pixel: Pixel.Event = .networkProtectionWaitlistRetriedInviteCodeRedemption - - do { - if let token = try tokenStore.fetchToken() { - DailyPixel.fireDailyAndCount(pixel: pixel, withAdditionalParameters: [ "tokenState": "found" ]) - } else { - DailyPixel.fireDailyAndCount(pixel: pixel, withAdditionalParameters: [ "tokenState": "nil" ]) - } - } catch { - DailyPixel.fireDailyAndCount(pixel: pixel, error: error, withAdditionalParameters: [ "tokenState": "error" ]) - } - - self?.fetchVPNWaitlistAuthToken(inviteCode: inviteCode) - } - } - return - } guard let inviteCode = VPNWaitlist.shared.waitlistStorage.getWaitlistInviteCode() else { @@ -95,7 +67,7 @@ extension AppDelegate { #endif private func checkWaitlistBackgroundTasks() { - guard vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() else { return } + guard vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() else { return } BGTaskScheduler.shared.getPendingTaskRequests { tasks in diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 1c141e9ae1..320ecc1636 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -75,7 +75,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION private let widgetRefreshModel = NetworkProtectionWidgetRefreshModel() private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults - lazy var vpnFeatureVisibilty = DefaultNetworkProtectionVisibility() + lazy var vpnFeatureVisibility = DefaultNetworkProtectionVisibility() #endif private var autoClear: AutoClear? @@ -304,7 +304,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { AppConfigurationFetch.registerBackgroundRefreshTaskHandler() #if NETWORK_PROTECTION - if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() { + if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() } #endif @@ -335,7 +335,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { setupSubscriptionsEnvironment() #endif - if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist() { + if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { clearDebugWaitlistState() } @@ -490,7 +490,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION widgetRefreshModel.refreshVPNWidget() - if vpnFeatureVisibilty.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { + if vpnFeatureVisibility.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { presentVPNEarlyAccessOverAlert() } @@ -817,7 +817,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION if shortcutItem.type == ShortcutKey.openVPNSettings { - presentNetworkProtectionStatusSettingsModal() + let visibility = DefaultNetworkProtectionVisibility() + if visibility.shouldShowVPNShortcut() { + presentNetworkProtectionStatusSettingsModal() + } } #endif @@ -852,7 +855,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func refreshShortcuts() { #if NETWORK_PROTECTION - guard vpnFeatureVisibilty.shouldShowVPNShortcut() else { + guard vpnFeatureVisibility.shouldShowVPNShortcut() else { UIApplication.shared.shortcutItems = nil return } @@ -929,7 +932,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { presentNetworkProtectionStatusSettingsModal() } - if vpnFeatureVisibilty.shouldKeepVPNAccessViaWaitlist(), identifier == VPNWaitlist.notificationIdentifier { + if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist(), identifier == VPNWaitlist.notificationIdentifier { presentNetworkProtectionWaitlistModal() DailyPixel.fire(pixel: .networkProtectionWaitlistNotificationLaunched) } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 3544f62240..688a45e365 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1427,10 +1427,14 @@ class MainViewController: UIViewController { Task { guard case .success(false) = await AccountManager().hasEntitlement(for: .networkProtection) else { return } - tunnelDefaults.enableEntitlementMessaging() - let controller = NetworkProtectionTunnelController() + + if await controller.isInstalled { + tunnelDefaults.enableEntitlementMessaging() + } + await controller.stop() + await controller.removeVPN() } } diff --git a/DuckDuckGo/NetworkProtectionAccessController.swift b/DuckDuckGo/NetworkProtectionAccessController.swift index 289addbfa4..a1acf99f11 100644 --- a/DuckDuckGo/NetworkProtectionAccessController.swift +++ b/DuckDuckGo/NetworkProtectionAccessController.swift @@ -143,7 +143,6 @@ struct NetworkProtectionAccessController: NetworkProtectionAccess { } func revokeNetworkProtectionAccess() { - networkProtectionWaitlistStorage.deleteWaitlistState() try? NetworkProtectionKeychainTokenStore().deleteToken() Task { diff --git a/DuckDuckGo/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/NetworkProtectionFeatureVisibility.swift index a698deb730..45f47e0153 100644 --- a/DuckDuckGo/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/NetworkProtectionFeatureVisibility.swift @@ -18,6 +18,7 @@ // import Foundation +import Subscription public protocol NetworkProtectionFeatureVisibility { func isWaitlistBetaActive() -> Bool @@ -49,8 +50,16 @@ public extension NetworkProtectionFeatureVisibility { !isPrivacyProLaunched() && isWaitlistBetaActive() && isWaitlistUser() } - // todo - https://app.asana.com/0/0/1206827703748771/f func shouldShowVPNShortcut() -> Bool { - isPrivacyProLaunched() || shouldKeepVPNAccessViaWaitlist() + if isPrivacyProLaunched() { +#if SUBSCRIPTION + let accountManager = AccountManager() + return accountManager.isUserAuthenticated +#else + return false +#endif + } else { + return shouldKeepVPNAccessViaWaitlist() + } } } diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index 1d130bf0cc..aaba16d63b 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -85,6 +85,13 @@ final class NetworkProtectionTunnelController: TunnelController { // MARK: - Connection Status Querying + var isInstalled: Bool { + get async { + let tunnelManager = await loadTunnelManager() + return tunnelManager != nil + } + } + /// Queries Network Protection to know if its VPN is connected. /// /// - Returns: `true` if the VPN is connected, connecting or reasserting, and `false` otherwise. diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index c15d879d28..d3f1a90a99 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -97,10 +97,13 @@ struct SettingsView: View { } }) - .onReceive(viewModel.$deepLinkTarget, perform: { link in - guard let link else { return } + .onReceive(viewModel.$deepLinkTarget.removeDuplicates(), perform: { link in + guard let link, link != self.deepLinkTarget else { + return + } + self.deepLinkTarget = link - + switch link.type { case .sheet: DispatchQueue.main.async { diff --git a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift index 24ce4c9297..579ed042c3 100644 --- a/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift +++ b/DuckDuckGoTests/NetworkProtectionFeatureVisibilityTests.swift @@ -60,28 +60,28 @@ final class NetworkProtectionFeatureVisibilityTests: XCTestCase { XCTAssertTrue(mockWithVPNAccess.shouldMonitorEntitlement()) XCTAssertFalse(mockWithVPNAccess.shouldKeepVPNAccessViaWaitlist()) XCTAssertTrue(mockWithVPNAccess.shouldShowThankYouMessaging()) - XCTAssertTrue(mockWithVPNAccess.shouldShowVPNShortcut()) + XCTAssertFalse(mockWithVPNAccess.shouldShowVPNShortcut()) // Not current waitlist user -> Enforce entitlement check, no more VPN use, no thank-you let mockWithBetaActive = baseMock.adding([.isWaitlistBetaActive]) XCTAssertTrue(mockWithBetaActive.shouldMonitorEntitlement()) XCTAssertFalse(mockWithBetaActive.shouldKeepVPNAccessViaWaitlist()) XCTAssertFalse(mockWithBetaActive.shouldShowThankYouMessaging()) - XCTAssertTrue(mockWithBetaActive.shouldShowVPNShortcut()) + XCTAssertFalse(mockWithBetaActive.shouldShowVPNShortcut()) // Waitlist beta OFF, current waitlist user -> Show thank-you, enforce entitlement check, no more VPN use let mockWithBetaInactive = baseMock.adding([.isWaitlistUser]) XCTAssertTrue(mockWithBetaInactive.shouldMonitorEntitlement()) XCTAssertFalse(mockWithBetaInactive.shouldKeepVPNAccessViaWaitlist()) XCTAssertTrue(mockWithBetaInactive.shouldShowThankYouMessaging()) - XCTAssertTrue(mockWithBetaInactive.shouldShowVPNShortcut()) + XCTAssertFalse(mockWithBetaInactive.shouldShowVPNShortcut()) - // Waitlist beta OFF, not current wailist user -> Enforce entitlement check, nothing else + // Waitlist beta OFF, not current waitlist user -> Enforce entitlement check, nothing else let mockWithNothingElse = baseMock XCTAssertTrue(mockWithNothingElse.shouldMonitorEntitlement()) XCTAssertFalse(mockWithNothingElse.shouldKeepVPNAccessViaWaitlist()) XCTAssertFalse(mockWithNothingElse.shouldShowThankYouMessaging()) - XCTAssertTrue(mockWithNothingElse.shouldShowVPNShortcut()) + XCTAssertFalse(mockWithNothingElse.shouldShowVPNShortcut()) } } From db22f863c4698db9c3f2995a56a5b7ba8ed79412 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Mon, 25 Mar 2024 15:51:53 +0000 Subject: [PATCH 163/245] disable variant selection by default (#2637) --- DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme index d3926becad..e564636928 100644 --- a/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme +++ b/DuckDuckGo.xcodeproj/xcshareddata/xcschemes/DuckDuckGo.xcscheme @@ -124,7 +124,7 @@ + isEnabled = "NO"> Date: Mon, 25 Mar 2024 13:22:24 -0400 Subject: [PATCH 164/245] Assign correct service environment (#2635) --- DuckDuckGo/AppDelegate.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 320ecc1636..4c3f946a0b 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -428,12 +428,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if SUBSCRIPTION private func setupSubscriptionsEnvironment() { Task { +#if DEBUG && ALPHA SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging +#else + SubscriptionPurchaseEnvironment.currentServiceEnvironment = .production +#endif + #if NETWORK_PROTECTION if VPNSettings(defaults: .networkProtectionGroupDefaults).selectedEnvironment == .staging { SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging } #endif + SubscriptionPurchaseEnvironment.current = .appStore } } From dde20abb16f7d9f4f1f9e34862ff9509f194d9ae Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Mon, 25 Mar 2024 22:57:54 +0100 Subject: [PATCH 165/245] Support partially failed purchase triggering "Subscription is being activated" state (#2639) --- DuckDuckGo/SettingsState.swift | 7 +- DuckDuckGo/SettingsSubscriptionView.swift | 7 +- DuckDuckGo/SettingsViewModel.swift | 64 ++++++++++++------- .../ViewModel/SubscriptionFlowViewModel.swift | 1 + 4 files changed, 50 insertions(+), 29 deletions(-) diff --git a/DuckDuckGo/SettingsState.swift b/DuckDuckGo/SettingsState.swift index c80e7c22ac..78441a3e00 100644 --- a/DuckDuckGo/SettingsState.swift +++ b/DuckDuckGo/SettingsState.swift @@ -44,6 +44,7 @@ struct SettingsState { var enabled: Bool var canPurchase: Bool var hasActiveSubscription: Bool + var isSubscriptionPendingActivation: Bool } struct SyncSettings { @@ -113,8 +114,10 @@ struct SettingsState { speechRecognitionAvailable: false, loginsEnabled: false, networkProtection: NetworkProtection(enabled: false, status: ""), - subscription: Subscription(enabled: false, canPurchase: false, - hasActiveSubscription: false), + subscription: Subscription(enabled: false, + canPurchase: false, + hasActiveSubscription: false, + isSubscriptionPendingActivation: false), sync: SyncSettings(enabled: false, title: "") ) } diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index c0e19bf564..508514cc93 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -182,7 +182,6 @@ struct SettingsSubscriptionView: View { var body: some View { if viewModel.state.subscription.enabled { Section(header: Text(UserText.settingsPProSection)) { - if viewModel.state.subscription.hasActiveSubscription { if !viewModel.isLoadingSubscriptionState { @@ -196,13 +195,13 @@ struct SettingsSubscriptionView: View { noEntitlementsAvailableView } } + } else if viewModel.state.subscription.isSubscriptionPendingActivation { + noEntitlementsAvailableView } else { purchaseSubscriptionView - } - } - + // Selected Feature handler for Subscription Flow .onChange(of: subscriptionFlowViewModel.selectedFeature) { value in guard let value else { return } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 9ce8c3e193..7d5e60818b 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -310,7 +310,8 @@ extension SettingsViewModel { var enabled = false var canPurchase = false var hasActiveSubscription = false - + var isSubscriptionPendingActivation = false + #if SUBSCRIPTION if #available(iOS 15, *) { enabled = isPrivacyProEnabled @@ -318,15 +319,21 @@ extension SettingsViewModel { await setupSubscriptionEnvironment() if let token = AccountManager().accessToken { let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token) - if case .success(let subscription) = subscriptionResult { + switch subscriptionResult { + case .success(let subscription): hasActiveSubscription = subscription.isActive + case .failure: + if await PurchaseManager.hasActiveSubscription() { + isSubscriptionPendingActivation = true + } } } } #endif return SettingsState.Subscription(enabled: enabled, canPurchase: canPurchase, - hasActiveSubscription: hasActiveSubscription) + hasActiveSubscription: hasActiveSubscription, + isSubscriptionPendingActivation: isSubscriptionPendingActivation) } private func getSyncState() -> SettingsState.SyncSettings { @@ -374,31 +381,42 @@ extension SettingsViewModel { // Fetch available subscriptions from the backend (or sign out) switch await SubscriptionService.getSubscription(accessToken: token) { - case .success(let subscription) where subscription.isActive: - - // Check entitlements and update UI accordingly - let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] - for entitlement in entitlements { - if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { - switch entitlement { - case .identityTheftRestoration: - self.shouldShowITP = result - case .dataBrokerProtection: - self.shouldShowDBP = result - case .networkProtection: - self.shouldShowNetP = result - case .unknown: - return + case .success(let subscription): + if subscription.isActive { + state.subscription.hasActiveSubscription = true + state.subscription.isSubscriptionPendingActivation = false + + // Check entitlements and update UI accordingly + let entitlements: [Entitlement.ProductName] = [.networkProtection, .dataBrokerProtection, .identityTheftRestoration] + for entitlement in entitlements { + if case let .success(result) = await AccountManager().hasEntitlement(for: entitlement) { + switch entitlement { + case .identityTheftRestoration: + self.shouldShowITP = result + case .dataBrokerProtection: + self.shouldShowDBP = result + case .networkProtection: + self.shouldShowNetP = result + case .unknown: + return + } } } + } else { + // Sign out in case subscription is no longer active + signOutUser() } - isLoadingSubscriptionState = false - - default: + + case .failure: // Account is active but there's not a valid subscription / entitlements - isLoadingSubscriptionState = false - signOutUser() + if await PurchaseManager.hasActiveSubscription() { + state.subscription.isSubscriptionPendingActivation = true + } else { + // Sign out in case access token is present but no subscription and there is no active transaction on Apple ID + signOutUser() + } } + isLoadingSubscriptionState = false } @available(iOS 15.0, *) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index caac67b827..6f00874786 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -149,6 +149,7 @@ final class SubscriptionFlowViewModel: ObservableObject { state.transactionError = .purchaseFailed case .missingEntitlements: isBackendError = true + state.shouldDismissView = true state.transactionError = .missingEntitlements case .failedToGetSubscriptionOptions: isStoreError = true From 619e799c109a2eed44a93b1fbea6349ef5c7a833 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Mon, 25 Mar 2024 22:51:22 -0400 Subject: [PATCH 166/245] Add launch pixels (#2638) --- Core/PixelEvent.swift | 9 +++++++++ DuckDuckGo/AppDelegate.swift | 15 +++++++++++++++ DuckDuckGo/MainViewController.swift | 10 ++++++++++ 3 files changed, 34 insertions(+) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index e03ecb6f1a..1a882e796c 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -601,6 +601,10 @@ extension Pixel { case privacyProSubscriptionManagementEmail case privacyProSubscriptionManagementPlanBilling case privacyProSubscriptionManagementRemoval + case privacyProFeatureEnabled + case privacyProPromotionDialogShownVPN + case privacyProVPNAccessRevokedDialogShown + case privacyProVPNBetaStoppedWhenPrivacyProEnabled // Full site address setting case settingsShowFullSiteAddressEnabled @@ -1186,6 +1190,11 @@ extension Pixel.Event { case .privacyProSubscriptionManagementRemoval: return "m_privacy-pro_settings_remove-from-device_click" case .settingsShowFullSiteAddressEnabled: return "m_settings_show_full_url_on" case .settingsShowFullSiteAddressDisabled: return "m_settings_show_full_url_off" + // Launch + case .privacyProFeatureEnabled: return "m_privacy-pro_feature_enabled" + case .privacyProPromotionDialogShownVPN: return "m_privacy-pro_promotion-dialog_shown_vpn" + case .privacyProVPNAccessRevokedDialogShown: return "m_privacy-pro_vpn-access-revoked-dialog_shown" + case .privacyProVPNBetaStoppedWhenPrivacyProEnabled: return "m_privacy-pro_vpn-beta-stopped-when-privacy-pro-enabled" // Web case .privacyProOfferMonthlyPriceClick: return "m_privacy-pro_offer_monthly-price_click" case .privacyProOfferYearlyPriceClick: return "m_privacy-pro_offer_yearly-price_click" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 4c3f946a0b..cdb468e6bf 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -386,6 +386,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #endif } window?.rootViewController?.present(alertController, animated: true) { [weak self] in + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNAccessRevokedDialogShown) self?.tunnelDefaults.showEntitlementAlert = false } } @@ -402,6 +403,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func presentVPNEarlyAccessOverAlert() { let alertController = CriticalAlerts.makeVPNEarlyAccessOverAlert() window?.rootViewController?.present(alertController, animated: true) { [weak self] in + DailyPixel.fireDailyAndCount(pixel: .privacyProPromotionDialogShownVPN) self?.tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true } } @@ -462,6 +464,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { StatisticsLoader.shared.load { StatisticsLoader.shared.refreshAppRetentionAtb() self.fireAppLaunchPixel() + self.firePrivacyProFeatureEnabledPixel() self.fireAppTPActiveUserPixel() } @@ -566,6 +569,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } } + private func firePrivacyProFeatureEnabledPixel() { +#if SUBSCRIPTION + let subscriptionFeatureAvailability = AppDependencyProvider.shared.subscriptionFeatureAvailability + guard subscriptionFeatureAvailability.isFeatureAvailable, + subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed else { + return + } + + DailyPixel.fire(pixel: .privacyProFeatureEnabled) +#endif + } + private func fireAppTPActiveUserPixel() { #if APP_TRACKING_PROTECTION guard AppDependencyProvider.shared.featureFlagger.isFeatureOn(.appTrackingProtection) else { diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 688a45e365..4f555e1323 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1403,6 +1403,7 @@ class MainViewController: UIViewController { } dismiss(animated: true) { self.present(alertController, animated: true, completion: nil) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNAccessRevokedDialogShown) self.tunnelDefaults.showEntitlementAlert = false } } @@ -1433,6 +1434,10 @@ class MainViewController: UIViewController { tunnelDefaults.enableEntitlementMessaging() } + if await controller.isConnected { + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + } + await controller.stop() await controller.removeVPN() } @@ -1442,6 +1447,11 @@ class MainViewController: UIViewController { private func onNetworkProtectionAccountSignOut(_ notification: Notification) { Task { let controller = NetworkProtectionTunnelController() + + if await controller.isConnected { + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + } + await controller.stop() await controller.removeVPN() } From a254140e1a1d0a78f8331352116b624f7b8c6647 Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Tue, 26 Mar 2024 10:44:51 +0100 Subject: [PATCH 167/245] Guard against Data Store warmup crash (#2626) --- Core/DataStoreWarmup.swift | 20 +++++++++++++++----- Core/PixelEvent.swift | 10 ++++++++-- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/Core/DataStoreWarmup.swift b/Core/DataStoreWarmup.swift index 79087b9fc2..0ef67ac0b6 100644 --- a/Core/DataStoreWarmup.swift +++ b/Core/DataStoreWarmup.swift @@ -34,26 +34,36 @@ public class DataStoreWarmup { private class BlockingNavigationDelegate: NSObject, WKNavigationDelegate { - let finished = PassthroughSubject() + var finished: PassthroughSubject? = PassthroughSubject() func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { return .allow } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - finished.send() + if let finished { + finished.send() + self.finished = nil + } else { + Pixel.fire(pixel: .webKitWarmupUnexpectedDidFinish, includedParameters: [.appVersion]) + } } func webViewWebContentProcessDidTerminate(_ webView: WKWebView) { Pixel.fire(pixel: .webKitDidTerminateDuringWarmup) - // We won't get a `didFinish` if the webview crashes - finished.send() + + if let finished { + finished.send() + self.finished = nil + } else { + Pixel.fire(pixel: .webKitWarmupUnexpectedDidTerminate, includedParameters: [.appVersion]) + } } var cancellable: AnyCancellable? func waitForLoad() async { await withCheckedContinuation { continuation in - cancellable = finished.sink { _ in + cancellable = finished?.sink { _ in continuation.resume() } } diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 1a882e796c..54f78f7f49 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -413,7 +413,10 @@ extension Pixel { case webKitDidTerminate case webKitTerminationDidReloadCurrentTab case webKitDidTerminateDuringWarmup - + + case webKitWarmupUnexpectedDidFinish + case webKitWarmupUnexpectedDidTerminate + case backgroundTaskSubmissionFailed case blankOverlayNotDismissed @@ -999,7 +1002,10 @@ extension Pixel.Event { case .webKitDidTerminate: return "m_d_wkt" case .webKitDidTerminateDuringWarmup: return "m_d_webkit-terminated-during-warmup" case .webKitTerminationDidReloadCurrentTab: return "m_d_wktct" - + + case .webKitWarmupUnexpectedDidFinish: return "m_d_webkit-warmup-unexpected-did-finish" + case .webKitWarmupUnexpectedDidTerminate: return "m_d_webkit-warmup-unexpected-did-terminate" + case .backgroundTaskSubmissionFailed: return "m_bt_rf" case .blankOverlayNotDismissed: return "m_d_ovs" From 5b81a5d484db0fe4a388d18339e5f5454a396385 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Tue, 26 Mar 2024 12:54:53 +0100 Subject: [PATCH 168/245] Fix forceful sign outs during purchase (#2641) --- DuckDuckGo/AppDelegate.swift | 2 -- DuckDuckGo/SettingsViewModel.swift | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index cdb468e6bf..3ffe9776bd 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -524,8 +524,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { cachePolicy: .reloadIgnoringLocalCacheData) { if subscription.isActive { DailyPixel.fire(pixel: .privacyProSubscriptionActive) - } else { - accountManager.signOut() } } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 7d5e60818b..2f877dd1a9 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -403,7 +403,9 @@ extension SettingsViewModel { } } } else { - // Sign out in case subscription is no longer active + // Sign out in case subscription is no longer active, reset the state + state.subscription.hasActiveSubscription = false + state.subscription.isSubscriptionPendingActivation = false signOutUser() } From 515891701e205139f1ede6c930155779feb18a45 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 26 Mar 2024 13:01:27 +0100 Subject: [PATCH 169/245] Subscriptions: 24. Properly dismiss view on selecting feature (#2640) --- .../Subscription/ViewModel/SubscriptionEmailViewModel.swift | 5 +++++ DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 638c1d313d..85156254be 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -152,6 +152,11 @@ final class SubscriptionEmailViewModel: ObservableObject { self.selectedFeature = .dbp } self.state.shouldDismissStack = true + + // Reset shouldDismissStack after dismissal to ensure it can be triggered again + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.state.shouldDismissStack = false + } } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 99fe2c826a..6371cad8e6 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -86,6 +86,10 @@ struct SubscriptionEmailView: View { shouldDisplayNavigationError = value } + .onChange(of: viewModel.state.shouldDismissStack) { _ in + onDismissStack?() + } + // Observe changes to shouldDismissView .onChange(of: viewModel.state.shouldDismissView) { shouldDismiss in if shouldDismiss { From cddb637650f7e0cc39bd08959369a75b15943994 Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Tue, 26 Mar 2024 13:21:17 +0100 Subject: [PATCH 170/245] Add support for local field validation for synced bookmarks and credentials (#2614) Task/Issue URL: https://app.asana.com/0/0/1206637913741251/f Description: This change adds a mechanism that filters out syncable objects that would fail validation on the backend before sending Sync patch request. Objects rejected from patch payload are retried on every subsequent Sync request, until they're updated to pass validation or deleted. --- Core/StringExtension.swift | 4 ++ Core/SyncBookmarksAdapter.swift | 2 +- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- DuckDuckGo/SettingsLegacyViewProvider.swift | 1 + DuckDuckGo/SyncSettingsViewController.swift | 33 +++++++++++- DuckDuckGo/en.lproj/Localizable.stringsdict | 54 +++++++++---------- DuckDuckGoTests/MockSecureVault.swift | 8 +++ .../Resources/en.lproj/Localizable.strings | 12 +++++ .../en.lproj/Localizable.stringsdict | 42 +++++++++++++++ .../ViewModels/SyncSettingsViewModel.swift | 2 + .../SyncUI/Views/Internal/UserText.swift | 14 +++++ .../SyncUI/Views/SyncSettingsView.swift | 8 +++ .../Views/SyncSettingsViewExtension.swift | 47 ++++++++++++++++ 14 files changed, 201 insertions(+), 32 deletions(-) create mode 100644 LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.stringsdict diff --git a/Core/StringExtension.swift b/Core/StringExtension.swift index b79146a7ea..7910fa553a 100644 --- a/Core/StringExtension.swift +++ b/Core/StringExtension.swift @@ -22,6 +22,10 @@ import BrowserServicesKit extension String { + public func truncated(length: Int, trailing: String = "…") -> String { + return (self.count > length) ? self.prefix(length) + trailing : self + } + /// Useful if loaded from UserText, for example public func format(arguments: CVarArg...) -> String { return String(format: self, arguments: arguments) diff --git a/Core/SyncBookmarksAdapter.swift b/Core/SyncBookmarksAdapter.swift index 0d3a4acf2f..9494349344 100644 --- a/Core/SyncBookmarksAdapter.swift +++ b/Core/SyncBookmarksAdapter.swift @@ -153,7 +153,7 @@ public final class SyncBookmarksAdapter { ) if !didMigrateToImprovedListsHandling { didMigrateToImprovedListsHandling = true - provider.lastSyncTimestamp = nil + provider.updateSyncTimestamps(server: nil, local: nil) } bindSyncErrorPublisher(provider) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2e5f393308..463f47ba3a 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 129.2.0; + version = 130.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 15e467a17c..6468ecca5d 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" : "284c328a097132a12e8abcf94d8f4d369063dcb4", - "version" : "129.2.0" + "revision" : "24da852f8726af668d9fdc6c5ea1c2d3b72e8888", + "version" : "130.0.0" } }, { diff --git a/DuckDuckGo/SettingsLegacyViewProvider.swift b/DuckDuckGo/SettingsLegacyViewProvider.swift index 7749d9bcb2..aa7fd200bf 100644 --- a/DuckDuckGo/SettingsLegacyViewProvider.swift +++ b/DuckDuckGo/SettingsLegacyViewProvider.swift @@ -91,6 +91,7 @@ class SettingsLegacyViewProvider: ObservableObject { var syncSettings: UIViewController { return SyncSettingsViewController(syncService: self.syncService, syncBookmarksAdapter: self.syncDataProviders.bookmarksAdapter, + syncCredentialsAdapter: self.syncDataProviders.credentialsAdapter, appSettings: self.appSettings) } diff --git a/DuckDuckGo/SyncSettingsViewController.swift b/DuckDuckGo/SyncSettingsViewController.swift index 7db8cd9667..c3ed122ced 100644 --- a/DuckDuckGo/SyncSettingsViewController.swift +++ b/DuckDuckGo/SyncSettingsViewController.swift @@ -31,6 +31,7 @@ class SyncSettingsViewController: UIHostingController { let syncService: DDGSyncing let syncBookmarksAdapter: SyncBookmarksAdapter + let syncCredentialsAdapter: SyncCredentialsAdapter var connector: RemoteConnecting? let userAuthenticator = UserAuthenticator(reason: UserText.syncUserUserAuthenticationReason) @@ -55,9 +56,15 @@ class SyncSettingsViewController: UIHostingController { var cancellables = Set() // For some reason, on iOS 14, the viewDidLoad wasn't getting called so do some setup here - init(syncService: DDGSyncing, syncBookmarksAdapter: SyncBookmarksAdapter, appSettings: AppSettings = AppDependencyProvider.shared.appSettings) { + init( + syncService: DDGSyncing, + syncBookmarksAdapter: SyncBookmarksAdapter, + syncCredentialsAdapter: SyncCredentialsAdapter, + appSettings: AppSettings = AppDependencyProvider.shared.appSettings + ) { self.syncService = syncService self.syncBookmarksAdapter = syncBookmarksAdapter + self.syncCredentialsAdapter = syncCredentialsAdapter let viewModel = SyncSettingsViewModel( isOnDevEnvironment: { syncService.serverEnvironment == .development }, @@ -72,6 +79,9 @@ class SyncSettingsViewController: UIHostingController { setUpFaviconsFetcherSwitch(viewModel) setUpFavoritesDisplayModeSwitch(viewModel, appSettings) setUpSyncPaused(viewModel, appSettings) + if DDGSync.isFieldValidationEnabled { + setUpSyncInvalidObjectsInfo(viewModel) + } setUpSyncFeatureFlags(viewModel) refreshForState(syncService.authState) @@ -186,6 +196,27 @@ class SyncSettingsViewController: UIHostingController { .store(in: &cancellables) } + private func setUpSyncInvalidObjectsInfo(_ viewModel: SyncSettingsViewModel) { + syncService.isSyncInProgressPublisher + .removeDuplicates() + .filter { !$0 } + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + self?.updateInvalidObjects(viewModel) + } + .store(in: &cancellables) + } + + private func updateInvalidObjects(_ viewModel: SyncSettingsViewModel) { + viewModel.invalidBookmarksTitles = syncBookmarksAdapter.provider? + .fetchDescriptionsForObjectsThatFailedValidation() + .map { $0.truncated(length: 15) } ?? [] + + let invalidCredentialsObjects: [String] = (try? syncCredentialsAdapter.provider?.fetchDescriptionsForObjectsThatFailedValidation()) ?? [] + viewModel.invalidCredentialsTitles = invalidCredentialsObjects.map({ $0.truncated(length: 15) }) + } + + override func viewDidLoad() { super.viewDidLoad() applyTheme(ThemeManager.shared.currentTheme) diff --git a/DuckDuckGo/en.lproj/Localizable.stringsdict b/DuckDuckGo/en.lproj/Localizable.stringsdict index df96547b47..92f5db2104 100644 --- a/DuckDuckGo/en.lproj/Localizable.stringsdict +++ b/DuckDuckGo/en.lproj/Localizable.stringsdict @@ -226,33 +226,33 @@ I blocked them! Are you sure you want to delete this password? - autofill.delete.all.passwords.confirmation.body - - NSStringLocalizedFormatKey - %1$#@passwords@ - passwords - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - d - other - Your passwords will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. - one - Your password will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. - - accounts - - NSStringFormatSpecTypeKey - NSStringPluralRuleType - NSStringFormatValueTypeKey - d - other - accounts - one - account - - + autofill.delete.all.passwords.confirmation.body + + NSStringLocalizedFormatKey + %1$#@passwords@ + passwords + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + Your passwords will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. + one + Your password will be deleted from this device. Make sure you still have a way to access your %2$#@accounts@. + + accounts + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + other + accounts + one + account + + autofill.delete.all.passwords.sync.confirmation.body NSStringLocalizedFormatKey diff --git a/DuckDuckGoTests/MockSecureVault.swift b/DuckDuckGoTests/MockSecureVault.swift index ad0cac1d33..3368501fed 100644 --- a/DuckDuckGoTests/MockSecureVault.swift +++ b/DuckDuckGoTests/MockSecureVault.swift @@ -210,6 +210,10 @@ final class MockSecureVault: AutofillSecureVault { [] } + func accountTitlesForSyncableCredentials(modifiedBefore date: Date) throws -> [String] { + [] + } + func deleteSyncableCredentials(_ syncableCredentials: SecureVaultModels.SyncableCredentials, in database: Database) throws { } @@ -403,6 +407,10 @@ class MockDatabaseProvider: AutofillDatabaseProvider { [] } + func modifiedSyncableCredentials(before date: Date) throws -> [SecureVaultModels.SyncableCredentials] { + [] + } + func syncableCredentialsForSyncIds(_ syncIds: any Sequence, in database: Database) throws -> [SecureVaultModels.SyncableCredentials] { [] } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.strings b/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.strings index 68f7f7409b..d43eb7ff8e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.strings +++ b/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.strings @@ -1,6 +1,12 @@ /* Standard Buttons - Back Button */ "back.button" = "Back"; +/* Do not translate - stringsdict entry */ +"bookmarks.invalid.objects.present.description" = "bookmarks.invalid.objects.present.description"; + +/* Alert title for invalid bookmarks being filtered out of synced data */ +"bookmarks.invalid.objects.present.title" = "Some bookmarks are not syncing due to excessively long content in certain fields."; + /* Sync Paused Errors - Bookmarks Limit Exceeded Action */ "bookmarks.limit.exceeded.action" = "Manage Bookmarks"; @@ -40,6 +46,12 @@ /* Connect With Server Sheet - Title */ "connect.with.server.sheet.title" = "Sync and Back Up This Device"; +/* Do not translate - stringsdict entry */ +"credentials.invalid.objects.present.description" = "credentials.invalid.objects.present.description"; + +/* Alert title for invalid logins being filtered out of synced data */ +"credentials.invalid.objects.present.title" = "Some logins are not syncing due to excessively long content in certain fields."; + /* Sync Paused Errors - Credentials Limit Exceeded Action */ "credentials.limit.exceeded.action" = "Manage Logins"; diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.stringsdict b/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.stringsdict new file mode 100644 index 0000000000..3b53f170f7 --- /dev/null +++ b/LocalPackages/SyncUI/Sources/SyncUI/Resources/en.lproj/Localizable.stringsdict @@ -0,0 +1,42 @@ + + + + + bookmarks.invalid.objects.present.description + + NSStringLocalizedFormatKey + %#@sites@ + sites + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Your bookmark for %2$@ can't sync because one of its fields exceeds the character limit. + one + Your bookmarks for %2$@ and 1 other site can't sync because some of their fields exceed the character limit. + other + Your bookmarks for %2$@ and %1$d other sites can't sync because some of their fields exceed the character limit. + + + credentials.invalid.objects.present.description + + NSStringLocalizedFormatKey + %#@sites@ + sites + + NSStringFormatSpecTypeKey + NSStringPluralRuleType + NSStringFormatValueTypeKey + d + zero + Your password for %2$@ can't sync because one of its fields exceeds the character limit. + one + Your passwords for %2$@ and 1 other site can't sync because some of their fields exceed the character limit. + other + Your passwords for %2$@ and %1$d other sites can't sync because some of their fields exceed the character limit. + + + + diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift index 93dd2b1df3..55ea76d867 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/SyncSettingsViewModel.swift @@ -81,6 +81,8 @@ public class SyncSettingsViewModel: ObservableObject { @Published public var isSyncingDevices = false @Published public var isSyncBookmarksPaused = false @Published public var isSyncCredentialsPaused = false + @Published public var invalidBookmarksTitles: [String] = [] + @Published public var invalidCredentialsTitles: [String] = [] @Published var isBusy = false @Published var recoveryCode = "" diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift index b36ab14850..9e92787079 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/Internal/UserText.swift @@ -48,6 +48,20 @@ public struct UserText { static let credentialsLimitExceededDescription = NSLocalizedString("credentials.limit.exceeded.description", bundle: Bundle.module, value: "Logins limit exceeded. Delete some to resume syncing.", comment: "Sync Paused Errors - Credentials Limit Exceeded Description") static let bookmarksLimitExceededAction = NSLocalizedString("bookmarks.limit.exceeded.action", bundle: Bundle.module, value: "Manage Bookmarks", comment: "Sync Paused Errors - Bookmarks Limit Exceeded Action") static let credentialsLimitExceededAction = NSLocalizedString("credentials.limit.exceeded.action", bundle: Bundle.module, value: "Manage Logins", comment: "Sync Paused Errors - Credentials Limit Exceeded Action") + // Sync Filtered Items Errors + static let invalidBookmarksPresentTitle = NSLocalizedString("bookmarks.invalid.objects.present.title", bundle: Bundle.module, value: "Some bookmarks are not syncing due to excessively long content in certain fields.", comment: "Alert title for invalid bookmarks being filtered out of synced data") + static let invalidCredentialsPresentTitle = NSLocalizedString("credentials.invalid.objects.present.title", bundle: Bundle.module, value: "Some logins are not syncing due to excessively long content in certain fields.", comment: "Alert title for invalid logins being filtered out of synced data") + + static func invalidBookmarksPresentDescription(_ invalidItemTitle: String, numberOfOtherInvalidItems: Int) -> String { + let message = NSLocalizedString("bookmarks.invalid.objects.present.description", bundle: Bundle.module, comment: "Do not translate - stringsdict entry") + return String(format: message, numberOfOtherInvalidItems, invalidItemTitle) + } + + static func invalidCredentialsPresentDescription(_ invalidItemTitle: String, numberOfOtherInvalidItems: Int) -> String { + let message = NSLocalizedString("credentials.invalid.objects.present.description", bundle: Bundle.module, comment: "Do not translate - stringsdict entry") + return String(format: message, numberOfOtherInvalidItems, invalidItemTitle) + } + // Synced Devices static let syncedDevicesSectionHeader = NSLocalizedString("synced.devices.section.header", bundle: Bundle.module, value: "Synced Devices", comment: "Synced Devices - Section Header") static let syncedDevicesThisDeviceLabel = NSLocalizedString("synced.devices.this.device.label", bundle: Bundle.module, value: "This Device", comment: "Synced Devices - This Device Label") diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift index fc442ce77c..085c048387 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsView.swift @@ -59,6 +59,14 @@ public struct SyncSettingsView: View { syncPaused(for: .credentials) } + if !model.invalidBookmarksTitles.isEmpty { + syncHasInvalidItems(for: .bookmarks) + } + + if !model.invalidCredentialsTitles.isEmpty { + syncHasInvalidItems(for: .credentials) + } + devices() options() diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift index bfec4047d5..6010da91b3 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/SyncSettingsViewExtension.swift @@ -286,6 +286,53 @@ extension SyncSettingsView { } } + @ViewBuilder + func syncHasInvalidItems(for itemType: LimitedItemType) -> some View { + var title: String { + switch itemType { + case .bookmarks: + return UserText.invalidBookmarksPresentTitle + case .credentials: + return UserText.invalidCredentialsPresentTitle + } + } + var description: String { + switch itemType { + case .bookmarks: + assert(!model.invalidBookmarksTitles.isEmpty) + let firstInvalidBookmarkTitle = model.invalidBookmarksTitles.first ?? "" + return UserText.invalidBookmarksPresentDescription( + firstInvalidBookmarkTitle, + numberOfOtherInvalidItems: model.invalidBookmarksTitles.count - 1 + ) + + case .credentials: + assert(!model.invalidCredentialsTitles.isEmpty) + let firstInvalidCredentialTitle = model.invalidCredentialsTitles.first ?? "" + return UserText.invalidCredentialsPresentDescription( + firstInvalidCredentialTitle, + numberOfOtherInvalidItems: model.invalidCredentialsTitles.count - 1 + ) + } + } + var actionTitle: String { + switch itemType { + case .bookmarks: + return UserText.bookmarksLimitExceededAction + case .credentials: + return UserText.credentialsLimitExceededAction + } + } + SyncWarningMessageView(title: title, message: description, buttonTitle: actionTitle) { + switch itemType { + case .bookmarks: + model.manageBookmarks() + case .credentials: + model.manageLogins() + } + } + } + @ViewBuilder func devEnvironmentIndicator() -> some View { if model.isOnDevEnvironment { From 8c606008f0f0d6bc15fa6590a371d31b047eacfc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 26 Mar 2024 17:46:57 +0100 Subject: [PATCH 171/245] Improves pixel information (#2636) Task/Issue URL: https://app.asana.com/0/0/1206919998699658/f macOS: https://github.com/duckduckgo/macos-browser/pull/2498 BSK: https://github.com/duckduckgo/BrowserServicesKit/pull/748 ## Description Adds some new errors to improve tracking of pixel errors. --- Core/Pixel.swift | 15 +-------------- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- 3 files changed, 4 insertions(+), 17 deletions(-) diff --git a/Core/Pixel.swift b/Core/Pixel.swift index 06786bb9ee..ef0ebe0850 100644 --- a/Core/Pixel.swift +++ b/Core/Pixel.swift @@ -271,16 +271,6 @@ private extension Pixel.Event { } -/// NSError supports this through `NSUnderlyingError`, but there's no support for this for Swift's `Error`. This protocol does that. -/// -/// The reason why this protocol returns a code and a domain instead of just an `Error` or `NSError` is so that the error implementing -/// this protocol has full control over these values, and is able to override them as it best sees fit. -/// -protocol ErrorWithUnderlyingError: Error { - var underlyingErrorCode: Int { get } - var underlyingErrorDomain: String { get } -} - extension Dictionary where Key == String, Value == String { mutating func appendErrorPixelParams(error: Error) { let nsError = error as NSError @@ -288,10 +278,7 @@ extension Dictionary where Key == String, Value == String { self[PixelParameters.errorCode] = "\(nsError.code)" self[PixelParameters.errorDomain] = nsError.domain - if let underlyingError = error as? ErrorWithUnderlyingError { - self[PixelParameters.underlyingErrorCode] = "\(underlyingError.underlyingErrorCode)" - self[PixelParameters.underlyingErrorDomain] = underlyingError.underlyingErrorDomain - } else if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { + if let underlyingError = nsError.userInfo["NSUnderlyingError"] as? NSError { self[PixelParameters.underlyingErrorCode] = "\(underlyingError.code)" self[PixelParameters.underlyingErrorDomain] = underlyingError.domain } else if let sqlErrorCode = nsError.userInfo["NSSQLiteErrorDomain"] as? NSNumber { diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 463f47ba3a..256b935058 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 130.0.0; + version = 130.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6468ecca5d..f39809e42c 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" : "24da852f8726af668d9fdc6c5ea1c2d3b72e8888", - "version" : "130.0.0" + "revision" : "c2ae796d25225dc59f4384170745759199e8e2a9", + "version" : "130.1.0" } }, { From 2429335581be32159aed47d9cfaaa37a7858a9fc Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 26 Mar 2024 20:45:18 +0100 Subject: [PATCH 172/245] macOS VPN: Remove server cache (#2642) Task/Issue URL: https://app.asana.com/0/0/1206931344651423/f iOS PR: https://github.com/duckduckgo/macos-browser/pull/2504 BSK PR: https://github.com/duckduckgo/BrowserServicesKit/pull/749 ## Description Remove unnecessary server list caching. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 4 ++-- .../EventMapping+NetworkProtectionError.swift | 9 +-------- DuckDuckGo/Feedback/VPNMetadataCollector.swift | 2 +- .../NetworkProtectionDebugViewController.swift | 1 + ...workProtectionVisibilityForTunnelProvider.swift | 2 +- .../NetworkProtectionPacketTunnelProvider.swift | 14 -------------- 7 files changed, 7 insertions(+), 27 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 256b935058..f0c3239aaf 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 130.1.0; + version = 131.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f39809e42c..a460573e3c 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" : "c2ae796d25225dc59f4384170745759199e8e2a9", - "version" : "130.1.0" + "revision" : "fa15af7be8ed109a31dd7554ab570ad556687998", + "version" : "131.0.0" } }, { diff --git a/DuckDuckGo/EventMapping+NetworkProtectionError.swift b/DuckDuckGo/EventMapping+NetworkProtectionError.swift index 8aaa5c559a..8e8b428b47 100644 --- a/DuckDuckGo/EventMapping+NetworkProtectionError.swift +++ b/DuckDuckGo/EventMapping+NetworkProtectionError.swift @@ -72,24 +72,17 @@ extension EventMapping where Event == NetworkProtectionError { pixelEvent = .networkProtectionNoAuthTokenFoundError case .vpnAccessRevoked: return - case - .noServerRegistrationInfo, + case .noServerRegistrationInfo, .couldNotSelectClosestServer, .couldNotGetPeerPublicKey, .couldNotGetPeerHostName, .couldNotGetInterfaceAddressRange, .failedToEncodeRegisterKeyRequest, - .noServerListFound, .serverListInconsistency, .failedToFetchRegisteredServers, .failedToFetchServerList, .failedToParseServerListResponse, .failedToParseRegisteredServersResponse, - .failedToEncodeServerList, - .failedToDecodeServerList, - .failedToWriteServerList, - .couldNotCreateServerListDirectory, - .failedToReadServerList, .wireGuardCannotLocateTunnelFileDescriptor, .wireGuardInvalidState, .wireGuardDnsResolution, diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index b858cf4672..3fb97ffc52 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -291,7 +291,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { enableSource: .init(from: accessManager.networkProtectionAccessType()), betaParticipant: accessType == .waitlistJoined, hasToken: hasToken, - subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).accessToken != nil + subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).isUserAuthenticated ) } } diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index d48adbac5e..a6b2a301c2 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -681,6 +681,7 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") vpnSettings.selectedEnvironment = .production } vpnSettings.selectedServer = .automatic + NetworkProtectionLocationListCompositeRepository.clearCache() tableView.reloadData() case .updateSubscriptionOverride: let defaults = UserDefaults.networkProtectionGroupDefaults diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift index 964641bf59..453c2380aa 100644 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift @@ -37,7 +37,7 @@ struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVis // todo - https://app.asana.com/0/0/1206844038943626/f func isPrivacyProLaunched() -> Bool { #if SUBSCRIPTION - AccountManager().accessToken != nil + AccountManager().isUserAuthenticated #else false #endif diff --git a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift index a2d7eb5989..9b86b6768f 100644 --- a/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift +++ b/PacketTunnelProvider/NetworkProtection/NetworkProtectionPacketTunnelProvider.swift @@ -149,20 +149,6 @@ final class NetworkProtectionPacketTunnelProvider: PacketTunnelProvider { pixelEvent = .networkProtectionClientInvalidAuthToken case .serverListInconsistency: return - case .failedToEncodeServerList: - pixelEvent = .networkProtectionServerListStoreFailedToEncodeServerList - case .failedToDecodeServerList: - pixelEvent = .networkProtectionServerListStoreFailedToDecodeServerList - case .failedToWriteServerList(let eventError): - pixelEvent = .networkProtectionServerListStoreFailedToWriteServerList - pixelError = eventError - case .noServerListFound: - return - case .couldNotCreateServerListDirectory: - return - case .failedToReadServerList(let eventError): - pixelEvent = .networkProtectionServerListStoreFailedToReadServerList - pixelError = eventError case .failedToCastKeychainValueToData(let field): pixelEvent = .networkProtectionKeychainErrorFailedToCastKeychainValueToData params[PixelParameters.keychainFieldName] = field From 244e9427b1a88709a80405eaf5aef4e7bbe4ce83 Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Tue, 26 Mar 2024 20:56:44 +0000 Subject: [PATCH 173/245] Update C-S-S (#2643) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f0c3239aaf..94b4ce3b49 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 131.0.0; + version = 131.1.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a460573e3c..f3fe67e4fa 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" : "fa15af7be8ed109a31dd7554ab570ad556687998", - "version" : "131.0.0" + "revision" : "00d3a6f0040479e1d814a3ae02d1a1dea8225bed", + "version" : "131.1.0" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "2f44185cca2edefbae7557393a61a23c282abbf8", - "version" : "5.7.0" + "revision" : "62d5dc3d02f6a8347dc5f0b52162a0107d38b74c", + "version" : "5.8.0" } }, { From abb103a17093c97e3e4d8d70a90d7cdc446fbb1c Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 27 Mar 2024 00:28:00 +0100 Subject: [PATCH 174/245] Add SUBSCRIPTION to Release configuration of main target (#2644) Co-authored-by: Sam Symons --- DuckDuckGo.xcodeproj/project.pbxproj | 4 ++-- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- DuckDuckGo/AppDelegate.swift | 2 +- DuckDuckGo/SettingsSubscriptionView.swift | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 94b4ce3b49..f9d24c45be 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8552,7 +8552,7 @@ MTL_ENABLE_DEBUG_INFO = NO; OTHER_LDFLAGS = "-ld_classic"; SDKROOT = iphoneos; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = NETWORK_PROTECTION; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "NETWORK_PROTECTION SUBSCRIPTION"; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 131.1.0; + version = 131.1.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f3fe67e4fa..34ffb53b21 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" : "00d3a6f0040479e1d814a3ae02d1a1dea8225bed", - "version" : "131.1.0" + "revision" : "8b7c87496990b49623e645c25e2198224e9493b2", + "version" : "131.1.1" } }, { @@ -183,7 +183,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 3ffe9776bd..d03a5e125f 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -430,7 +430,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if SUBSCRIPTION private func setupSubscriptionsEnvironment() { Task { -#if DEBUG && ALPHA +#if DEBUG || ALPHA SubscriptionPurchaseEnvironment.currentServiceEnvironment = .staging #else SubscriptionPurchaseEnvironment.currentServiceEnvironment = .production diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index 508514cc93..c95851a8a1 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -180,7 +180,7 @@ struct SettingsSubscriptionView: View { } var body: some View { - if viewModel.state.subscription.enabled { + if viewModel.state.subscription.enabled && viewModel.state.subscription.canPurchase { Section(header: Text(UserText.settingsPProSection)) { if viewModel.state.subscription.hasActiveSubscription { From 2871971bc83dd549a27f3a00013b86cb3388baa4 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 27 Mar 2024 09:15:35 -0400 Subject: [PATCH 175/245] Remove obsolete comments (#2645) --- DuckDuckGo/DefaultNetworkProtectionVisibility.swift | 2 -- DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift | 2 -- 2 files changed, 4 deletions(-) diff --git a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift index 406a8100ce..e71f0ead81 100644 --- a/DuckDuckGo/DefaultNetworkProtectionVisibility.swift +++ b/DuckDuckGo/DefaultNetworkProtectionVisibility.swift @@ -74,7 +74,6 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return hasLegacyAuthToken || hasBeenInvited } - // todo - https://app.asana.com/0/0/1206844038943626/f func isPrivacyProLaunched() -> Bool { if let subscriptionOverrideEnabled = userDefaults.subscriptionOverrideEnabled { #if ALPHA || DEBUG @@ -87,7 +86,6 @@ struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { return AppDependencyProvider.shared.subscriptionFeatureAvailability.isFeatureAvailable } - // todo - https://app.asana.com/0/0/1206844038943626/f func shouldMonitorEntitlement() -> Bool { isPrivacyProLaunched() } diff --git a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift index 453c2380aa..2b50eee28d 100644 --- a/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtectionVisibilityForTunnelProvider.swift @@ -34,7 +34,6 @@ struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVis preconditionFailure("Does not apply to Tunnel Provider") } - // todo - https://app.asana.com/0/0/1206844038943626/f func isPrivacyProLaunched() -> Bool { #if SUBSCRIPTION AccountManager().isUserAuthenticated @@ -43,7 +42,6 @@ struct NetworkProtectionVisibilityForTunnelProvider: NetworkProtectionFeatureVis #endif } - // todo - https://app.asana.com/0/0/1206844038943626/f func shouldMonitorEntitlement() -> Bool { isPrivacyProLaunched() } From c8b7f43e0f3000a9b2842ccb7549acad15ddb392 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Wed, 27 Mar 2024 14:33:00 +0100 Subject: [PATCH 176/245] Cleans up some pixels we're no longer using (#2649) Task/Issue URL: https://app.asana.com/0/414235014887631/1206940927974460/f ## Description Removes some unused pixels. --- Core/PixelEvent.swift | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 54f78f7f49..4db2056522 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -319,11 +319,6 @@ extension Pixel { case networkProtectionClientFailedToParseRedeemResponse case networkProtectionClientInvalidAuthToken - case networkProtectionServerListStoreFailedToEncodeServerList - case networkProtectionServerListStoreFailedToDecodeServerList - case networkProtectionServerListStoreFailedToWriteServerList - case networkProtectionServerListStoreFailedToReadServerList - case networkProtectionKeychainErrorFailedToCastKeychainValueToData case networkProtectionKeychainReadError case networkProtectionKeychainWriteError @@ -913,10 +908,6 @@ extension Pixel.Event { case .networkProtectionClientFailedToRedeemInviteCode: return "m_netp_backend_api_error_failed_to_redeem_invite_code" case .networkProtectionClientFailedToParseRedeemResponse: return "m_netp_backend_api_error_parsing_redeem_response_failed" case .networkProtectionClientInvalidAuthToken: return "m_netp_backend_api_error_invalid_auth_token" - case .networkProtectionServerListStoreFailedToEncodeServerList: return "m_netp_storage_error_failed_to_encode_server_list" - case .networkProtectionServerListStoreFailedToDecodeServerList: return "m_netp_storage_error_failed_to_decode_server_list" - case .networkProtectionServerListStoreFailedToWriteServerList: return "m_netp_storage_error_server_list_file_system_write_failed" - case .networkProtectionServerListStoreFailedToReadServerList: return "m_netp_storage_error_server_list_file_system_read_failed" case .networkProtectionKeychainErrorFailedToCastKeychainValueToData: return "m_netp_keychain_error_failed_to_cast_keychain_value_to_data" case .networkProtectionKeychainReadError: return "m_netp_keychain_error_read_failed" case .networkProtectionKeychainWriteError: return "m_netp_keychain_error_write_failed" From 87ee3b9a756085f73d6ad8d8fd011e52c4543e7f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 27 Mar 2024 15:40:18 +0100 Subject: [PATCH 177/245] Subscriptions: 25 - Navigation Fix + State.cleanup (#2648) Task/Issue URL: https://app.asana.com/0/1204099484721401/1206905872297213/f Description: Ignores WKWebView 999 errors which triggered a false positive Improve memory management of UserScripts/Subfeatures --- .../HeadlessWebViewCoordinator.swift | 15 +++++++++-- ...scriptionPagesUseSubscriptionFeature.swift | 4 +++ .../SubscriptionEmailViewModel.swift | 8 +++++- .../ViewModel/SubscriptionFlowViewModel.swift | 26 +++++++++---------- .../Views/SubscriptionFlowView.swift | 7 ++--- .../Views/SubscriptionRestoreView.swift | 1 - 6 files changed, 41 insertions(+), 20 deletions(-) diff --git a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift index d8fc66a461..5acd4df139 100644 --- a/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift +++ b/DuckDuckGo/Subscription/AsyncHeadlessWebview/HeadlessWebViewCoordinator.swift @@ -188,11 +188,22 @@ extension HeadlessWebViewCoordinator: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: any Error) { - onNavigationError?(error) + handleWebViewError(error) + } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { - onNavigationError?(error) + handleWebViewError(error) + } + + private func handleWebViewError(_ error: Error) { + let NSError = error as NSError + // Check for the specific NSURLErrorDomain and the cancelled error code -999 and ignore + if NSError.domain == NSURLErrorDomain && NSError.code == -999 { + return + } else { + onNavigationError?(error) + } } // Javascript Confirm dialogs Delegate diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index c22131fd2d..b9f6d8695e 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -413,6 +413,10 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec setTransactionStatus(.idle) setTransactionError(nil) broker = nil + onFeatureSelected = nil + onSetSubscription = nil + onActivateSubscription = nil + onBackToSettings = nil } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 85156254be..6a27977bb5 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -79,7 +79,6 @@ final class SubscriptionEmailViewModel: ObservableObject { Task { await initializeView() - await setupSubscribers() } setupObservers() } @@ -99,9 +98,15 @@ final class SubscriptionEmailViewModel: ObservableObject { func onAppear() { Task { await initializeView() } + Task { await setupSubscribers() } webViewModel.navigationCoordinator.navigateTo(url: emailURL ) } + func onDissappear() { + cancellables.removeAll() + canGoBackCancellable = nil + } + @MainActor private func initializeView() { if accountManager.isUserAuthenticated { @@ -219,6 +224,7 @@ final class SubscriptionEmailViewModel: ObservableObject { deinit { cancellables.removeAll() + canGoBackCancellable = nil } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 6f00874786..8c61b4f8cb 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -250,28 +250,28 @@ final class SubscriptionFlowViewModel: ObservableObject { state.canNavigateBack = enabled } - func initializeViewData() async { + @MainActor + func onAppear() async { + DispatchQueue.main.async { + self.resetState() + self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL ) + } Pixel.fire(pixel: .privacyProOfferScreenImpression, debounce: 2) await self.setupTransactionObserver() await self .setupWebViewObservers() } - - @MainActor - func onAppear() { - resetState() - } - - @MainActor + func onDisappear() { - resetState() + DispatchQueue.main.async { + self.resetState() + } + canGoBackCancellable?.cancel() + cancellables.removeAll() } @MainActor private func resetState() { - self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL ) - self.selectedFeature = nil - self.state.shouldDismissView = false - self.state.shouldActivateSubscription = false + self.state = State() } @MainActor diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 99ec18a506..bd11a7d3ea 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -161,8 +161,8 @@ struct SubscriptionFlowView: View { .onAppear(perform: { setUpAppearances() - Task { await viewModel.initializeViewData() } - viewModel.onAppear() + Task { await viewModel.onAppear() } + }) .onDisappear(perform: { @@ -200,7 +200,8 @@ struct SubscriptionFlowView: View { title: Text(UserText.subscriptionAppStoreErrorTitle), message: Text(UserText.subscriptionAppStoreErrorMessage), dismissButton: .cancel(Text(UserText.actionOK)) { - Task { await viewModel.initializeViewData() } + viewModel.clearTransactionError() + viewModel.finalizeSubscriptionFlow() } ) case .backend, .general: diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index af5c7941d8..75448465e4 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -147,7 +147,6 @@ struct SubscriptionRestoreView: View { } .onAppear { - viewModel.initializeView() viewModel.onAppear() setUpAppearances() } From 6b67dc4e927304bc2beae96b245b85345f93c4b5 Mon Sep 17 00:00:00 2001 From: Brad Slayter Date: Wed, 27 Mar 2024 09:49:35 -0500 Subject: [PATCH 178/245] Fix param typo and remove vpnOn from toggle report (#2650) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f9d24c45be..41c90a1a51 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 131.1.1; + version = 131.1.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 34ffb53b21..746a1dca1b 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -183,7 +183,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" From 2bb87a1308d73c5096047aa6e4b4f040958cbc45 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 27 Mar 2024 16:25:14 +0100 Subject: [PATCH 179/245] 26. Subscriptions: Force email fetch on view appear (#2652) Task/Issue URL: https://app.asana.com/0/0/1206939258527736/f Description: Refreshed account email when presenting the "Add to another device view" --- .../ViewModel/SubscriptionRestoreViewModel.swift | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 4ed77dc2a4..af9b090ae1 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -82,6 +82,15 @@ final class SubscriptionRestoreViewModel: ObservableObject { @MainActor func onAppear() { resetState() + Task { + guard let token = accountManager.accessToken else { return } + if case .success(let details) = await accountManager.fetchAccountDetails(with: token) { + DispatchQueue.main.async { + self.state.subscriptionEmail = details.email + } + } + } + } @MainActor From 4d14f4b4f5060a951588143aa298c9cdd3babb2a Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 27 Mar 2024 16:28:34 +0100 Subject: [PATCH 180/245] Fix convenience init for Account Manager that omits the subscriptionAppGroup param (#2651) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 41c90a1a51..db49869954 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 131.1.2; + version = 132.0.0; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 746a1dca1b..22c7397eca 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" : "8b7c87496990b49623e645c25e2198224e9493b2", - "version" : "131.1.1" + "revision" : "dfb35745561322b2b80f8be0810ddb2c806e077e", + "version" : "132.0.0" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "43a6e1c1864846679a254e60c91332c3fbd922ee", - "version" : "3.3.0" + "revision" : "620921fea14569eb00745cb5a44890d5890d99ec", + "version" : "3.4.0" } }, { From a9f0167d2bbd6cf9db72003395bbb0240f94aded Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Wed, 27 Mar 2024 16:13:58 +0000 Subject: [PATCH 181/245] Subscription pixels update (#2653) Task/Issue URL: https://app.asana.com/0/1205842942115003/1205469290776415/f **Description**: Update to some Subscription Pixels that were lost during a recent refactoring --- DuckDuckGo/SettingsSubscriptionView.swift | 9 +++--- ...scriptionPagesUseSubscriptionFeature.swift | 2 +- .../SubscriptionEmailViewModel.swift | 5 +--- .../SubscriptionRestoreViewModel.swift | 1 - .../Views/SubscriptionRestoreView.swift | 28 +++++++++++-------- .../Views/SubscriptionSettingsView.swift | 2 +- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index c95851a8a1..d032e18e81 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -40,7 +40,7 @@ struct SettingsSubscriptionView: View { static let navigationDelay = 0.3 static let infoIcon = "info-16" } - + private var subscriptionDescriptionView: some View { VStack(alignment: .leading) { Text(UserText.settingsPProSubscribe).daxBodyRegular() @@ -113,12 +113,11 @@ struct SettingsSubscriptionView: View { .sheet(isPresented: $isShowingSubscriptionRestoreFlow, onDismiss: { Task { viewModel.onAppear() } }, content: { - SubscriptionRestoreView(viewModel: subscriptionRestoreViewModel).interactiveDismissDisabled() - }) - + SubscriptionRestoreView(viewModel: subscriptionRestoreViewModel).interactiveDismissDisabled() + }) } } - + @ViewBuilder private var noEntitlementsAvailableView: some View { Group { diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index b9f6d8695e..7845e5ee46 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -225,6 +225,7 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec // Check for active subscriptions if await PurchaseManager.hasActiveSubscription() { setTransactionError(.hasActiveSubscription) + Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) return nil } @@ -248,7 +249,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec setTransactionError(.accountCreationFailed) case .activeSubscriptionAlreadyPresent: setTransactionError(.hasActiveSubscription) - Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) default: setTransactionError(.purchaseFailed) } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 6a27977bb5..8b8f283f11 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -133,6 +133,7 @@ final class SubscriptionEmailViewModel: ObservableObject { private func setupObservers() { // Feature Callback subFeature.onSetSubscription = { + DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailSuccess) UniquePixel.fire(pixel: .privacyProSubscriptionActivated) DispatchQueue.main.async { self.state.subscriptionActive = true @@ -212,10 +213,6 @@ final class SubscriptionEmailViewModel: ObservableObject { state.shouldDisplayInactiveError = true } - private func completeActivation() { - DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailSuccess) - } - func dismissView() { DispatchQueue.main.async { self.state.shouldDismissView = true diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index af9b090ae1..8a1678c8f9 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -75,7 +75,6 @@ final class SubscriptionRestoreViewModel: ObservableObject { } func initializeView() { - Pixel.fire(pixel: .privacyProSettingsAddDevice) Task { await setupTransactionObserver() } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index 75448465e4..a85d37b1ff 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -27,15 +27,21 @@ import Core @available(iOS 15.0, *) // swiftlint:disable type_body_length struct SubscriptionRestoreView: View { - + + enum Source { + case addAnotherDevice + case settings + } + @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionRestoreViewModel() - + @StateObject var viewModel: SubscriptionRestoreViewModel = SubscriptionRestoreViewModel() + @State private var isAlertVisible = false @State private var shouldShowWelcomePage = false @State private var shouldNavigateToActivationFlow = false @State var isModal = true - + var source: SubscriptionRestoreView.Source = .settings + private enum Constants { static let heroImage = "ManageSubscriptionHero" static let appleIDIcon = "Platform-Apple-16-subscriptions" @@ -61,7 +67,6 @@ struct SubscriptionRestoreView: View { static let buttonCornerRadius = 8.0 static let buttonInsets = EdgeInsets(top: 10.0, leading: 16.0, bottom: 10.0, trailing: 16.0) static let buttonTopPadding: CGFloat = 20 - } var body: some View { @@ -133,27 +138,28 @@ struct SubscriptionRestoreView: View { .onChange(of: shouldNavigateToActivationFlow) { result in viewModel.showActivationFlow(result) } - .onChange(of: viewModel.state.shouldDismissView) { result in if result { dismiss() } } - .onChange(of: viewModel.state.shouldShowPlans) { result in if result { dismiss() } } - .onAppear { viewModel.onAppear() setUpAppearances() + + switch source { + case .addAnotherDevice: + Pixel.fire(pixel: .privacyProSettingsAddDevice, debounce: 2) + default: break + } } - } - - + // MARK: - @ViewBuilder diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index 098ca6aa6b..f175232a3c 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -89,7 +89,7 @@ struct SubscriptionSettingsView: View { private var devicesSection: some View { Section(header: Text(UserText.subscriptionManageDevices)) { - NavigationLink(destination: SubscriptionRestoreView(isModal: false)) { + NavigationLink(destination: SubscriptionRestoreView(isModal: false, source: .addAnotherDevice)) { SettingsCustomCell(content: { Text(UserText.subscriptionAddDeviceButton) .daxBodyRegular() From a0304cdb270c660944024810aba8fc09a54c3470 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 27 Mar 2024 18:04:18 +0100 Subject: [PATCH 182/245] Update the metadata (#2654) --- fastlane/metadata/en-US/description.txt | 48 ++++++++++++++----------- fastlane/metadata/en-US/keywords.txt | 2 +- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/fastlane/metadata/en-US/description.txt b/fastlane/metadata/en-US/description.txt index e16d809a31..4720b87e7a 100644 --- a/fastlane/metadata/en-US/description.txt +++ b/fastlane/metadata/en-US/description.txt @@ -1,27 +1,35 @@ DuckDuckGo is a free browser that provides the most comprehensive online privacy protection in one app. Unlike most popular browsers, it has powerful privacy protections by default, including our search engine that doesn’t track your history and over a dozen other built-in protections. Millions of people use DuckDuckGo as their go-to browser to protect their everyday online activities, from searching to browsing, emailing, and more. -  + FEATURE HIGHLIGHTS -• Search Privately by Default - DuckDuckGo Private Search comes built-in, so you can easily search the web without being tracked. - -• Block Tracking Cookies While Browsing - Prevent most 3rd-party Internet cookies from tracking you as you browse online. -  -• Escape Website Trackers Before They Load - Automatically stop most hidden trackers (3rd-party scripts) from loading, which prevents companies from collecting and using any personal data from these trackers. Stay secure with our cutting-edge tracker-blocking technology – 3rd-Party Tracker Loading Protection, which goes above and beyond what you get in most popular web browsers by default. -  -• Automatically Enforce Encryption - Force many sites you visit to automatically use an encrypted (HTTPS) connection, which helps shield your data from Wi-Fi snoopers and network onlookers like your Internet provider. -  -• Block Email Trackers (Beta) - Over 85% of emails sent to Duck Addresses (@duck.com) contain trackers that can detect when you’ve opened a message, where you were when you opened it, and what device you were using. Email Protection makes it easy to block most email trackers and hide your existing address when signing up for things online, all without switching email providers. - -• Escape Fingerprinting - Help stop companies from creating a unique identifier for you by blocking their attempts to combine specific information about your web browser and device settings. -  -We feature many protections not available on most Internet browsers (even incognito browsers), including protection from link tracking, Google AMP tracking, and more. -  +• Search Privately by Default: DuckDuckGo Private Search comes built-in, so you can easily search online without being tracked. + +• Block Most Trackers Before They Load: Our 3rd-Party Tracker Loading Protection exceeds what most popular browsers offer by default, stopping hidden tracking scripts from loading and collecting your personal data. + +• Enable Built-in Email Protection: Block most email trackers and hide your existing email address with @duck.com addresses. + +• Automatically Enforce Encryption: Shield your data from network and Wi-Fi snoopers by forcing many sites to use an HTTPS connection. + +• Escape Fingerprinting: Make it harder for companies to create a unique identifier for you by blocking attempts to combine info about your browser and device. + +• Sync and Backup: Securely sync bookmarks and passwords across your devices. + +We feature many protections not available on most browsers, even in private browsing mode, including protection from link tracking, AMP tracking, and more. + + EVERYDAY PRIVACY CONTROLS -• Tap Fire Button, Burn Data - Clear your tabs and browsing data fast with our Fire Button. -  -• Signal Your Privacy Preference with Global Privacy Control (GPC) - Built in to our app, GPC intends to help you express your opt-out rights automatically by telling websites not to sell or share your personal information. Whether it can be used to enforce your legal rights (for example, current or future CCPA, GDPR requirements) depends on the laws in your jurisdiction. +• Clear your tabs and browsing data in a flash with the Fire Button. -Read more about our free Tracking Protections at https://help.duckduckgo.com/privacy/web-tracking-protections +• Banish cookie pop-ups and automatically set your preferences to minimize cookies and maximize privacy.   + +• Signal Your Privacy Preference with Global Privacy Control (GPC) built into our app. GPC intends to help you express your opt-out rights automatically by telling websites not to sell or share your personal info. Whether it can be used to enforce your legal rights depends on the laws in your jurisdiction. + +Privacy Pro Pricing & Terms  -You don't need to wait to take back your privacy. Join the millions of people using DuckDuckGo and protect many of your everyday online activities with one app. It's privacy, simplified. +Payment will be charged automatically to your iTunes account until you cancel, which you can do in app settings. You have the option to provide an email address to activate your subscription on other devices, and we will only use that email address to verify your subscription. For terms of service and privacy policy, visit https://duckduckgo.com/pro/privacy-terms + +You don't need to wait to take back your privacy. Join the millions of people using DuckDuckGo and protect many of your everyday online activities with one app. It's privacy, simplified. + +Read more about our free Tracking Protections at https://help.duckduckgo.com/privacy/web-tracking-protections Privacy Policy: https://duckduckgo.com/privacy/ +Terms of Service: https://duckduckgo.com/terms \ No newline at end of file diff --git a/fastlane/metadata/en-US/keywords.txt b/fastlane/metadata/en-US/keywords.txt index 3b6f2b7f4b..f9cae6671c 100644 --- a/fastlane/metadata/en-US/keywords.txt +++ b/fastlane/metadata/en-US/keywords.txt @@ -1 +1 @@ -web,privacy,search,email,secure,online,tracker,duc,duck,go,data,ddg,incognito,ad,block,vpn,internet \ No newline at end of file +web,encrypt,search,email,secure,online,tracker,duc,duck,go,data,ddg,incognito,ad,block,vpn,internet \ No newline at end of file From e8ac0bbde0f898d18c2cd45f4c26ebca1d5a789e Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 27 Mar 2024 21:05:17 +0100 Subject: [PATCH 183/245] Release 7.113.0-0 (#2655) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 426 ++++++++++++------ DuckDuckGo.xcodeproj/project.pbxproj | 56 +-- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 5 files changed, 322 insertions(+), 168 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index cd6af05c70..1505c5ea25 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.112.0 +MARKETING_VERSION = 7.113.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 8e97b6a832..2e95e5c71c 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"c595f46fe54bfa96bbff4f30fc3940d8\"" - public static let embeddedDataSHA = "911e6616b6869c0940c492240d43c0cf60274755dd45a50cc635c8b7c792cb87" + public static let embeddedDataETag = "\"4cac4f65624262686a265d3b95a8374b\"" + public static let embeddedDataSHA = "6da9ab104a4b2adca51862ad942821e629a24929a95016b045f7bdd0028e1f71" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index d2593f8526..dd4ef388f4 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1710501855617, + "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,18 +252,12 @@ { "domain": "metro.co.uk" }, - { - "domain": "youtube.com" - }, { "domain": "newsmax.com" }, { "domain": "meneame.net" }, - { - "domain": "espn.com" - }, { "domain": "usaa.com" }, @@ -274,13 +265,13 @@ "domain": "publico.es" }, { - "domain": "cnbc.com" + "domain": "leboncoin.fr" }, { - "domain": "earth.google.com" + "domain": "www.ffbb.com" }, { - "domain": "instructure.com" + "domain": "earth.google.com" }, { "domain": "iscorp.com" @@ -298,13 +289,15 @@ "settings": { "disabledCMPs": [ "generic-cosmetic", - "termsfeed3" + "termsfeed3", + "strato.de" ] }, "state": "enabled", "features": { "onByDefault": { - "state": "disabled", + "state": "enabled", + "minSupportedVersion": "7.113.0", "rollout": { "steps": [ { @@ -320,7 +313,7 @@ } } }, - "hash": "9aaa080c235ddd8df4295c4d73c87a94" + "hash": "3149ef7db2b6835e6d0dc7c2a843cfff" }, "autofill": { "exceptions": [ @@ -978,9 +971,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1006,16 +996,13 @@ } }, "state": "disabled", - "hash": "9c70121360bcdfeb63770d8d9aeee770" + "hash": "36e8971fa9bb204b78a5929a14a108dd" }, "clickToPlay": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1036,7 +1023,7 @@ } }, "state": "disabled", - "hash": "ba97e20bd75a4dcd4ef376ec9b7fccc1" + "hash": "4390af06f967ef97a827aeab0ac0d1ca" }, "clientBrandHint": { "exceptions": [], @@ -1070,9 +1057,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1083,7 +1067,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "910e25ffe4d683b3c708a1578d097a16" + "hash": "e37447d42ee8194f185e35e40f577f41" }, "cookie": { "settings": { @@ -1128,9 +1112,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1142,7 +1123,7 @@ } ], "state": "disabled", - "hash": "7c7ceca9eeb664059750ea96938669b0" + "hash": "37a27966915571085613911b47e6e2eb" }, "customUserAgent": { "settings": { @@ -1266,9 +1247,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -1713,6 +1691,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": [ @@ -1755,6 +1741,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", @@ -1768,6 +1755,7 @@ "anzeige", "close ad", "close this ad", + "content continues below", "x", "_", "sponsored", @@ -1847,6 +1835,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": [ @@ -2084,6 +2099,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": [ @@ -2102,6 +2142,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": [ @@ -2968,6 +3033,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": [ @@ -3277,6 +3359,14 @@ } ] }, + { + "domain": "prajwaldesai.com", + "rules": [ + { + "type": "disable-default" + } + ] + }, { "domain": "primagames.com", "rules": [ @@ -3318,6 +3408,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" } ] }, @@ -3394,6 +3504,15 @@ } ] }, + { + "domain": "runnersworld.com", + "rules": [ + { + "selector": ".ad-disclaimer", + "type": "closest-empty" + } + ] + }, { "domain": "scmp.com", "rules": [ @@ -3619,6 +3738,18 @@ } ] }, + { + "domain": "thetvdb.com", + "rules": [ + { + "type": "disable-default" + }, + { + "selector": "[data-aa-adunit]", + "type": "hide" + } + ] + }, { "domain": "thewindowsclub.com", "rules": [ @@ -3740,6 +3871,14 @@ } ] }, + { + "domain": "tumblr.com", + "rules": [ + { + "type": "disable-default" + } + ] + }, { "domain": "tvtropes.org", "rules": [ @@ -3894,6 +4033,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" } ] }, @@ -4037,16 +4192,13 @@ ] }, "state": "enabled", - "hash": "2f1178300a22f85803bc42c676ea2cab" + "hash": "313fb06b3f83c0fbe6ac7e7c358ee38e" }, "exceptionHandler": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4058,7 +4210,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "fingerprintingAudio": { "state": "disabled", @@ -4069,9 +4221,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4082,7 +4231,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "40b13d6ca36cd3de287345ab9e5839fb" + "hash": "f25a8f2709e865c2bd743828c7ee2f77" }, "fingerprintingBattery": { "exceptions": [ @@ -4092,9 +4241,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4106,7 +4252,7 @@ } ], "state": "enabled", - "hash": "038608803499bebc30460a84ed27579f" + "hash": "440f8d663d59430c93d66208655d9238" }, "fingerprintingCanvas": { "settings": { @@ -4200,9 +4346,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4214,7 +4357,7 @@ } ], "state": "disabled", - "hash": "98b5e91ff539dfb6c81699e32b76f70c" + "hash": "ea4c565bae27996f0d651300d757594c" }, "fingerprintingHardware": { "settings": { @@ -4260,9 +4403,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4274,7 +4414,7 @@ } ], "state": "enabled", - "hash": "ed0d208ef9ffcba9851eddf68a005583" + "hash": "46fbcd4738329731c1b11e88e3afcb7b" }, "fingerprintingScreenSize": { "settings": { @@ -4317,9 +4457,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4331,7 +4468,7 @@ } ], "state": "enabled", - "hash": "264749fcf7f5e7e03478bb6f0df4a48a" + "hash": "0fb22f84b750e0d29bad55bd95d9ce2b" }, "fingerprintingTemporaryStorage": { "exceptions": [ @@ -4347,9 +4484,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4361,16 +4495,13 @@ } ], "state": "enabled", - "hash": "c8f4dcd850359636b47ebc31a26f1f1d" + "hash": "f1632b92379847c92c95bcffefbc1bd2" }, "googleRejected": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4382,7 +4513,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "gpc": { "state": "enabled", @@ -4420,9 +4551,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4445,7 +4573,7 @@ "privacy-test-pages.site" ] }, - "hash": "d1dd05d2cbbb9425a925cc162aaa681f" + "hash": "549a6e76edaf16c1fffced31b97e9553" }, "harmfulApis": { "settings": { @@ -4550,9 +4678,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4564,7 +4689,7 @@ } ], "state": "disabled", - "hash": "9d0f5f4f8c02e79246e2d809cada2fdb" + "hash": "44d3e707cba3ee0a3578f52dc2ce2aa4" }, "history": { "state": "enabled", @@ -4586,9 +4711,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4599,7 +4721,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "ea6d5ad048e35c75c451bff6fe58cb11" + "hash": "f772808ed34cc9ea8cbcbb7cdaf74429" }, "incontextSignup": { "exceptions": [], @@ -4637,9 +4759,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4658,7 +4777,7 @@ ] }, "state": "enabled", - "hash": "f8dc40f1f5687f403f381452d66eb0d0" + "hash": "698de7b963d7d7942c5c5d1e986bb1b1" }, "networkProtection": { "state": "enabled", @@ -4687,7 +4806,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" @@ -4699,8 +4834,7 @@ "domain": "sundancecatalog.com" } ], - "state": "disabled", - "hash": "d07b5bf740e4d648c94e1ac65c4305d9" + "hash": "38558d5e7b231d4b27e7dd76814387a7" }, "privacyDashboard": { "exceptions": [], @@ -4712,7 +4846,7 @@ } }, "toggleReports": { - "state": "internal", + "state": "enabled", "rollout": { "steps": [ { @@ -4723,7 +4857,23 @@ } }, "state": "enabled", - "hash": "0d76cb4a367fc6738f7c4aa6a66f0a04" + "hash": "f7cce63c16c142db4ff5764b542a6c52" + }, + "privacyPro": { + "state": "enabled", + "exceptions": [], + "features": { + "isLaunched": { + "state": "disabled" + }, + "isLaunchedOverride": { + "state": "disabled" + }, + "allowPurchase": { + "state": "enabled" + } + }, + "hash": "5dc8a8fec03c9993bce2b6ec95779903" }, "privacyProtectionsPopup": { "state": "disabled", @@ -4753,9 +4903,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4767,7 +4914,7 @@ } ], "state": "disabled", - "hash": "1679be76968fe50858b3cc664b8fcbad" + "hash": "0d3df0f7c24ebde89d2dced4e2d34322" }, "requestFilterer": { "state": "disabled", @@ -4775,9 +4922,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4791,7 +4935,7 @@ "settings": { "windowInMs": 0 }, - "hash": "219a51a9aafbc9c1bae4bad55d7ce437" + "hash": "0fff8017d8ea4b5609b8f5c110be1401" }, "runtimeChecks": { "state": "disabled", @@ -4799,9 +4943,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4813,16 +4954,13 @@ } ], "settings": {}, - "hash": "e2246d7c78df2167134e1428b04d51ca" + "hash": "800a19533c728bbec7e31e466f898268" }, "serviceworkerInitiatedRequests": { "exceptions": [ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -4834,7 +4972,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "sync": { "state": "enabled", @@ -5042,9 +5180,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" ] } ] @@ -5106,6 +5245,7 @@ "rule": "c.amazon-adsystem.com/aax2/apstag.js", "domains": [ "applesfera.com", + "fattoincasadabenedetta.it", "inquirer.com", "thesurfersview.com", "wildrivers.lostcoastoutpost.com" @@ -5819,6 +5959,12 @@ "" ] }, + { + "rule": "go.ezodn.com", + "domains": [ + "airplaneacademy.com" + ] + }, { "rule": "ezodn.com", "domains": [ @@ -6160,6 +6306,7 @@ "hscprojects.com", "kits4beats.com", "magicgameworld.com", + "ncaa.com", "rocketnews24.com", "youmath.it", "zefoy.com" @@ -6909,6 +7056,12 @@ "domains": [ "" ] + }, + { + "rule": "cdn.optimizely.com/js/24096340716.js", + "domains": [ + "hgtv.com" + ] } ] }, @@ -7132,6 +7285,12 @@ "domains": [ "newser.com" ] + }, + { + "rule": "a.pub.network/core/prebid-universal-creative.js", + "domains": [ + "titantv.com" + ] } ] }, @@ -7284,6 +7443,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": [ { @@ -7852,9 +8027,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7865,7 +8037,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "4d2da0fe5691d4283ebfb1021270c6ea" + "hash": "9d0207772c29e4b74f7dd0356c9f84d9" }, "trackingCookies1p": { "settings": { @@ -7878,9 +8050,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7892,7 +8061,7 @@ } ], "state": "disabled", - "hash": "bfd8b32efe8d633fe670bf6ab1b00240" + "hash": "4dddf681372a2aea9788090b13db6e6f" }, "trackingCookies3p": { "settings": { @@ -7902,9 +8071,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7916,7 +8082,7 @@ } ], "state": "disabled", - "hash": "d07b5bf740e4d648c94e1ac65c4305d9" + "hash": "841fa92b9728c9754f050662678f82c7" }, "trackingParameters": { "exceptions": [ @@ -7926,9 +8092,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7972,7 +8135,7 @@ ] }, "state": "enabled", - "hash": "f64c29121e46b2c79c23e8e7efc58c59" + "hash": "1df4ca1a649e81401fb5e872212b4dd0" }, "userAgentRotation": { "settings": { @@ -7982,9 +8145,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -7996,7 +8156,7 @@ } ], "state": "disabled", - "hash": "4498ff835bed7ce27ff2a568db599155" + "hash": "f65d10dfdf6739feab99a08d42734747" }, "voiceSearch": { "exceptions": [], @@ -8008,9 +8168,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -8061,7 +8218,7 @@ } ] }, - "hash": "1b8acba9eed9ba83fdfe0da1e9d8db87" + "hash": "592a1fb6314f04875fc44a66ef7c2433" }, "windowsPermissionUsage": { "exceptions": [], @@ -8073,9 +8230,6 @@ { "domain": "earth.google.com" }, - { - "domain": "instructure.com" - }, { "domain": "iscorp.com" }, @@ -8087,7 +8241,7 @@ } ], "state": "disabled", - "hash": "2b0b6ee567814d75aa2646d494a45a78" + "hash": "5e792dd491428702bc0104240fbce0ce" }, "windowsWaitlist": { "exceptions": [], diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index db49869954..fb74e7bbf6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8262,7 +8262,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8299,7 +8299,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8391,7 +8391,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8419,7 +8419,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8569,7 +8569,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8595,7 +8595,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8660,7 +8660,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8695,7 +8695,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8729,7 +8729,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8760,7 +8760,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9047,7 +9047,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9078,7 +9078,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9107,7 +9107,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9141,7 +9141,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9172,7 +9172,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9205,11 +9205,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9443,7 +9443,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9470,7 +9470,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9503,7 +9503,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9541,7 +9541,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9577,7 +9577,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9612,11 +9612,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9790,11 +9790,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9823,10 +9823,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index beab9dd941..0a06622ca2 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.112.0 + 7.113.0 Key version Title From 0bb1510faff00ccb53b8ee3e99450735569e6032 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Wed, 27 Mar 2024 17:36:28 -0400 Subject: [PATCH 184/245] When Thank You messaging is shown, stop the tunnel (#2656) --- DuckDuckGo/AppDelegate.swift | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index d03a5e125f..79240a52de 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -499,9 +499,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { #if NETWORK_PROTECTION widgetRefreshModel.refreshVPNWidget() - if vpnFeatureVisibility.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { - presentVPNEarlyAccessOverAlert() - } + stopTunnelAndShowThankYouMessagingIfNeeded() if tunnelDefaults.showEntitlementAlert { presentExpiredEntitlementAlert() @@ -513,6 +511,23 @@ class AppDelegate: UIResponder, UIApplicationDelegate { updateSubscriptionStatus() } + private func stopTunnelAndShowThankYouMessagingIfNeeded() { + if vpnFeatureVisibility.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { + presentVPNEarlyAccessOverAlert() + + Task { + let controller = NetworkProtectionTunnelController() + + if await controller.isConnected { + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + } + + await controller.stop() + await controller.removeVPN() + } + } + } + func updateSubscriptionStatus() { #if SUBSCRIPTION Task { From b956b2ade69452f8b16205f47f7b4a7e155a67f2 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 27 Mar 2024 22:49:37 +0100 Subject: [PATCH 185/245] Release 7.113.0-1 (#2657) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fb74e7bbf6..039f0c9a7d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8262,7 +8262,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8299,7 +8299,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8391,7 +8391,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8419,7 +8419,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8569,7 +8569,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8595,7 +8595,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8660,7 +8660,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8695,7 +8695,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8729,7 +8729,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8760,7 +8760,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9047,7 +9047,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9078,7 +9078,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9107,7 +9107,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9141,7 +9141,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9172,7 +9172,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9205,11 +9205,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9443,7 +9443,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9470,7 +9470,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9503,7 +9503,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9541,7 +9541,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9577,7 +9577,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9612,11 +9612,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9790,11 +9790,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9823,10 +9823,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 23064ad8d375ae63e2cf4a2d83ad957aa7c7c79f Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Fri, 29 Mar 2024 18:02:21 -0700 Subject: [PATCH 186/245] Improve subscription library sync failure error reporting (#2658) Task/Issue URL: https://app.asana.com/0/414235014887631/1206946506094035/f Tech Design URL: CC: Description: This PR updates BSK to include a subscription library logging change. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 039f0c9a7d..1892d0aac7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10033,7 +10033,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 132.0.0; + version = 132.0.1; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 22c7397eca..7ab16c7ee5 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" : "dfb35745561322b2b80f8be0810ddb2c806e077e", - "version" : "132.0.0" + "revision" : "73f68ee1c0dda3cd4a0b0cc3cc38a6cc7e605829", + "version" : "132.0.1" } }, { From a3d14e3e18e8aeecada5344c2b2f130f2dec86ca Mon Sep 17 00:00:00 2001 From: Alessandro Boron Date: Tue, 2 Apr 2024 14:41:45 +1100 Subject: [PATCH 187/245] Update Lottie to 4.4.1 (#2631) Task/Issue URL: https://app.asana.com/0/414235014887631/1206751893829139/f Description: Update Lottie to version 4.4.1 to align with macOS and comply with Apple's Privacy manifest for 3rd party SDK. --- DuckDuckGo.xcodeproj/project.pbxproj | 34 +++++++++---------- .../xcshareddata/swiftpm/Package.resolved | 8 ++--- DuckDuckGo/Base.lproj/OmniBar.xib | 18 +++++----- DuckDuckGo/FireButtonAnimator.swift | 10 +++--- DuckDuckGo/LottieView.swift | 8 ++--- DuckDuckGo/MenuButton.swift | 6 ++-- DuckDuckGo/PrivacyIconView.swift | 20 +++++------ DuckDuckGo/PrivacyInfoContainerView.swift | 18 +++++----- DuckDuckGo/TabSwitcherButton.swift | 6 ++-- DuckDuckGo/ViewHighlighter.swift | 2 +- 10 files changed, 66 insertions(+), 64 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 1892d0aac7..fcc93ce1c0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -262,7 +262,6 @@ 4B0295192537BC6700E00CEF /* ConfigurationDebugViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0295182537BC6700E00CEF /* ConfigurationDebugViewController.swift */; }; 4B0F3F502B9BFF2100392892 /* NetworkProtectionFAQView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */; }; 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */; }; - 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2754EB29E8C7DF00394032 /* Lottie */; }; 4B37E0502B928CA6009E81CA /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B37E04F2B928CA6009E81CA /* vpn-light-mode.json */; }; 4B470ED6299C49800086EBDC /* AppTrackingProtectionDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B470ED5299C49800086EBDC /* AppTrackingProtectionDatabase.swift */; }; 4B470ED9299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4B470ED7299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodeld */; }; @@ -636,6 +635,7 @@ 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1DB217B373E0011A0D4 /* DarkTheme.swift */; }; 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; }; 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; }; + 9F8FE9492BAE50E50071E372 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9F8FE9482BAE50E50071E372 /* Lottie */; }; AA3D854523D9942200788410 /* AppIconSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */; }; AA3D854723D9E88E00788410 /* AppIconSettingsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854623D9E88E00788410 /* AppIconSettingsCell.swift */; }; AA3D854923DA1DFB00788410 /* AppIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3D854823DA1DFB00788410 /* AppIcon.swift */; }; @@ -2737,12 +2737,12 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 9F8FE9492BAE50E50071E372 /* Lottie in Frameworks */, 853273B624FFE0BB00E3C778 /* WidgetKit.framework in Frameworks */, 0238E44F29C0FAA100615E30 /* FindInPageIOSJSSupport in Frameworks */, 3760DFED299315EF0045A446 /* Waitlist in Frameworks */, F1D43AFA2B99C1D300BAB743 /* BareBonesBrowserKit in Frameworks */, F143C2EB1E4A4CD400CFDE3A /* Core.framework in Frameworks */, - 4B2754EC29E8C7DF00394032 /* Lottie in Frameworks */, 31E69A63280F4CB600478327 /* DuckUI in Frameworks */, CB941A6E2B96AB08000F9E7A /* PrivacyDashboard in Frameworks */, F42D541D29DCA40B004C4FF1 /* DesignResourcesKit in Frameworks */, @@ -5770,9 +5770,9 @@ 3760DFEC299315EF0045A446 /* Waitlist */, F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */, 0238E44E29C0FAA100615E30 /* FindInPageIOSJSSupport */, - 4B2754EB29E8C7DF00394032 /* Lottie */, CB941A6D2B96AB08000F9E7A /* PrivacyDashboard */, F1D43AF92B99C1D300BAB743 /* BareBonesBrowserKit */, + 9F8FE9482BAE50E50071E372 /* Lottie */, ); productName = DuckDuckGo; productReference = 84E341921E2F7EFB00BDBA6F /* DuckDuckGo.app */; @@ -6082,10 +6082,10 @@ F42D541B29DCA40B004C4FF1 /* XCRemoteSwiftPackageReference "DesignResourcesKit" */, 0202568C29881E4300E694E7 /* XCRemoteSwiftPackageReference "CocoaAsyncSocket" */, 0238E44D29C0FAA100615E30 /* XCRemoteSwiftPackageReference "ios-js-support" */, - 4B2754EA29E8C7DF00394032 /* XCRemoteSwiftPackageReference "lottie-ios" */, 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */, B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */, F1D43AF82B99C1D300BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */, + 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */, ); productRefGroup = 84E341931E2F7EFB00BDBA6F /* Products */; projectDirPath = ""; @@ -10012,14 +10012,6 @@ version = 2.0.0; }; }; - 4B2754EA29E8C7DF00394032 /* XCRemoteSwiftPackageReference "lottie-ios" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/duckduckgo/lottie-ios.git"; - requirement = { - kind = exactVersion; - version = 3.3.0; - }; - }; 854007E52B57FB020001BD98 /* XCRemoteSwiftPackageReference "ZIPFoundation" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/weichsel/ZIPFoundation.git"; @@ -10036,6 +10028,14 @@ version = 132.0.1; }; }; + 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/airbnb/lottie-spm.git"; + requirement = { + kind = exactVersion; + version = 4.4.1; + }; + }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/apple-toolbox.git"; @@ -10151,11 +10151,6 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = SyncDataProviders; }; - 4B2754EB29E8C7DF00394032 /* Lottie */ = { - isa = XCSwiftPackageProductDependency; - package = 4B2754EA29E8C7DF00394032 /* XCRemoteSwiftPackageReference "lottie-ios" */; - productName = Lottie; - }; 4B948E2529DCCDB9002531FA /* Persistence */ = { isa = XCSwiftPackageProductDependency; package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -10225,6 +10220,11 @@ package = 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Bookmarks; }; + 9F8FE9482BAE50E50071E372 /* Lottie */ = { + isa = XCSwiftPackageProductDependency; + package = 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */; + productName = Lottie; + }; B6F997CD2B8F380D00476735 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 7ab16c7ee5..58ea241b57 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -100,12 +100,12 @@ } }, { - "identity" : "lottie-ios", + "identity" : "lottie-spm", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/lottie-ios.git", + "location" : "https://github.com/airbnb/lottie-spm.git", "state" : { - "revision" : "abf5510e261c85ffddd29de0bca9b72592ea2bdd", - "version" : "3.3.0" + "revision" : "3bd43e12d6fb54654366a61f7cfaca787318b8ce", + "version" : "4.4.1" } }, { diff --git a/DuckDuckGo/Base.lproj/OmniBar.xib b/DuckDuckGo/Base.lproj/OmniBar.xib index b72d90e54d..e74cc5e6fc 100644 --- a/DuckDuckGo/Base.lproj/OmniBar.xib +++ b/DuckDuckGo/Base.lproj/OmniBar.xib @@ -1,9 +1,9 @@ - + - + @@ -93,13 +93,13 @@ - + - + - + @@ -133,16 +133,16 @@ - + - + - + - + diff --git a/DuckDuckGo/FireButtonAnimator.swift b/DuckDuckGo/FireButtonAnimator.swift index ef8c2af543..b5aca8dc41 100644 --- a/DuckDuckGo/FireButtonAnimator.swift +++ b/DuckDuckGo/FireButtonAnimator.swift @@ -46,9 +46,9 @@ enum FireButtonAnimationType: String, CaseIterable, Identifiable, CustomStringCo } } - var composition: Animation? { + var composition: LottieAnimation? { guard let fileName = fileName else { return nil } - return Animation.named(fileName, animationCache: LRUAnimationCache.sharedCache) + return LottieAnimation.named(fileName, animationCache: DefaultAnimationCache.sharedCache) } var transition: Double { @@ -98,8 +98,8 @@ enum FireButtonAnimationType: String, CaseIterable, Identifiable, CustomStringCo class FireButtonAnimator { private let appSettings: AppSettings - private var preLoadedComposition: Animation? - + private var preLoadedComposition: LottieAnimation? + init(appSettings: AppSettings) { self.appSettings = appSettings reloadPreLoadedComposition() @@ -133,7 +133,7 @@ class FireButtonAnimator { window.addSubview(snapshot) - let animationView = AnimationView(animation: composition) + let animationView = LottieAnimationView(animation: composition) let currentAnimation = appSettings.currentFireButtonAnimation let speed = currentAnimation.speed animationView.contentMode = .scaleAspectFill diff --git a/DuckDuckGo/LottieView.swift b/DuckDuckGo/LottieView.swift index dc44a51579..c6143a769f 100644 --- a/DuckDuckGo/LottieView.swift +++ b/DuckDuckGo/LottieView.swift @@ -40,8 +40,8 @@ struct LottieView: UIViewRepresentable { var isAnimating: Binding private let loopMode: LoopMode - let animationView = AnimationView() - + let animationView = LottieAnimationView() + init(lottieFile: String, delay: TimeInterval = 0, loopMode: LoopMode = .mode(.playOnce), isAnimating: Binding = .constant(true)) { self.lottieFile = lottieFile self.delay = delay @@ -49,8 +49,8 @@ struct LottieView: UIViewRepresentable { self.loopMode = loopMode } - func makeUIView(context: Context) -> some AnimationView { - animationView.animation = Animation.named(lottieFile) + func makeUIView(context: Context) -> some LottieAnimationView { + animationView.animation = LottieAnimation.named(lottieFile) animationView.contentMode = .scaleAspectFit animationView.clipsToBounds = false diff --git a/DuckDuckGo/MenuButton.swift b/DuckDuckGo/MenuButton.swift index 999618f248..1f29040c37 100644 --- a/DuckDuckGo/MenuButton.swift +++ b/DuckDuckGo/MenuButton.swift @@ -54,7 +54,7 @@ class MenuButton: UIView { private let bookmarksIconView = UIImageView() - let anim = AnimationView(name: "menu_light") + let anim = LottieAnimationView(name: "menu_light") let pointerView: UIView = UIView(frame: CGRect(x: 0, y: 0, width: Constants.pointerViewWidth, @@ -190,9 +190,9 @@ extension MenuButton: Themable { switch theme.currentImageSet { case .light: - anim.animation = Animation.named("menu_light") + anim.animation = LottieAnimation.named("menu_light") case .dark: - anim.animation = Animation.named("menu_dark") + anim.animation = LottieAnimation.named("menu_dark") } if currentState == State.closeImage { diff --git a/DuckDuckGo/PrivacyIconView.swift b/DuckDuckGo/PrivacyIconView.swift index aa5e2267fb..cd287e5050 100644 --- a/DuckDuckGo/PrivacyIconView.swift +++ b/DuckDuckGo/PrivacyIconView.swift @@ -28,12 +28,12 @@ enum PrivacyIcon { class PrivacyIconView: UIView { @IBOutlet var daxLogoImageView: UIImageView! - @IBOutlet var staticShieldAnimationView: AnimationView! - @IBOutlet var staticShieldDotAnimationView: AnimationView! - - @IBOutlet var shieldAnimationView: AnimationView! - @IBOutlet var shieldDotAnimationView: AnimationView! - + @IBOutlet var staticShieldAnimationView: LottieAnimationView! + @IBOutlet var staticShieldDotAnimationView: LottieAnimationView! + + @IBOutlet var shieldAnimationView: LottieAnimationView! + @IBOutlet var shieldDotAnimationView: LottieAnimationView! + public required init?(coder aDecoder: NSCoder) { icon = .shield @@ -54,15 +54,15 @@ class PrivacyIconView: UIView { updateAccessibilityLabels(for: icon) } - func loadAnimations(for theme: Theme, animationCache cache: AnimationCacheProvider = LRUAnimationCache.sharedCache) { + func loadAnimations(for theme: Theme, animationCache cache: AnimationCacheProvider = DefaultAnimationCache.sharedCache) { let useLightStyle = theme.currentImageSet == .light - let shieldAnimation = Animation.named(useLightStyle ? "shield" : "dark-shield", animationCache: cache) + let shieldAnimation = LottieAnimation.named(useLightStyle ? "shield" : "dark-shield", animationCache: cache) shieldAnimationView.animation = shieldAnimation staticShieldAnimationView.animation = shieldAnimation staticShieldAnimationView.currentProgress = 0.0 - let shieldWithDotAnimation = Animation.named(useLightStyle ? "shield-dot" : "dark-shield-dot", animationCache: cache) + let shieldWithDotAnimation = LottieAnimation.named(useLightStyle ? "shield-dot" : "dark-shield-dot", animationCache: cache) shieldDotAnimationView.animation = shieldWithDotAnimation staticShieldDotAnimationView.animation = shieldWithDotAnimation staticShieldDotAnimationView.currentProgress = 1.0 @@ -128,7 +128,7 @@ class PrivacyIconView: UIView { daxLogoImageView.isHidden = true } - func shieldAnimationView(for icon: PrivacyIcon) -> AnimationView? { + func shieldAnimationView(for icon: PrivacyIcon) -> LottieAnimationView? { switch icon { case .shield: return shieldAnimationView diff --git a/DuckDuckGo/PrivacyInfoContainerView.swift b/DuckDuckGo/PrivacyInfoContainerView.swift index 713d760b3f..73b3ff8907 100644 --- a/DuckDuckGo/PrivacyInfoContainerView.swift +++ b/DuckDuckGo/PrivacyInfoContainerView.swift @@ -27,9 +27,9 @@ class PrivacyInfoContainerView: UIView { @IBOutlet var privacyIcon: PrivacyIconView! @IBOutlet var maskingView: UIView! - @IBOutlet var trackers1Animation: AnimationView! - @IBOutlet var trackers2Animation: AnimationView! - @IBOutlet var trackers3Animation: AnimationView! + @IBOutlet var trackers1Animation: LottieAnimationView! + @IBOutlet var trackers2Animation: LottieAnimationView! + @IBOutlet var trackers3Animation: LottieAnimationView! override func awakeFromNib() { super.awakeFromNib() @@ -41,24 +41,26 @@ class PrivacyInfoContainerView: UIView { [trackers1Animation, trackers2Animation, trackers3Animation].forEach { animationView in animationView.contentMode = .scaleAspectFill animationView.backgroundBehavior = .pauseAndRestore + // Trackers animation do not render properly using Lottie CoreAnimation. Running them on the CPU seems working fine. + animationView.configuration = LottieConfiguration(renderingEngine: .mainThread) } loadAnimations(for: ThemeManager.shared.currentTheme) } - private func loadAnimations(for theme: Theme, animationCache cache: AnimationCacheProvider = LRUAnimationCache.sharedCache) { + private func loadAnimations(for theme: Theme, animationCache cache: AnimationCacheProvider = DefaultAnimationCache.sharedCache) { let useLightStyle = theme.currentImageSet == .light - trackers1Animation.animation = Animation.named(useLightStyle ? "trackers-1" : "dark-trackers-1", animationCache: cache) - trackers2Animation.animation = Animation.named(useLightStyle ? "trackers-2" : "dark-trackers-2", animationCache: cache) - trackers3Animation.animation = Animation.named(useLightStyle ? "trackers-3" : "dark-trackers-3", animationCache: cache) + trackers1Animation.animation = LottieAnimation.named(useLightStyle ? "trackers-1" : "dark-trackers-1", animationCache: cache) + trackers2Animation.animation = LottieAnimation.named(useLightStyle ? "trackers-2" : "dark-trackers-2", animationCache: cache) + trackers3Animation.animation = LottieAnimation.named(useLightStyle ? "trackers-3" : "dark-trackers-3", animationCache: cache) privacyIcon.loadAnimations(for: theme, animationCache: cache) currentlyLoadedStyle = theme.currentImageSet } - func trackerAnimationView(for trackerCount: Int) -> AnimationView? { + func trackerAnimationView(for trackerCount: Int) -> LottieAnimationView? { switch trackerCount { case 0: return nil case 1: return trackers1Animation diff --git a/DuckDuckGo/TabSwitcherButton.swift b/DuckDuckGo/TabSwitcherButton.swift index c3e888ba86..27a4f6f2d5 100644 --- a/DuckDuckGo/TabSwitcherButton.swift +++ b/DuckDuckGo/TabSwitcherButton.swift @@ -48,7 +48,7 @@ class TabSwitcherButton: UIView { var workItem: DispatchWorkItem? - let anim = AnimationView(name: "new_tab") + let anim = LottieAnimationView(name: "new_tab") let label = UILabel() let pointerView: UIView = UIView(frame: CGRect(x: 0, y: 0, @@ -201,9 +201,9 @@ extension TabSwitcherButton: Themable { switch theme.currentImageSet { case .light: - anim.animation = Animation.named("new_tab_dark") + anim.animation = LottieAnimation.named("new_tab_dark") case .dark: - anim.animation = Animation.named("new_tab") + anim.animation = LottieAnimation.named("new_tab") } addSubview(anim) diff --git a/DuckDuckGo/ViewHighlighter.swift b/DuckDuckGo/ViewHighlighter.swift index 8a479d4a4d..f208154305 100644 --- a/DuckDuckGo/ViewHighlighter.swift +++ b/DuckDuckGo/ViewHighlighter.swift @@ -33,7 +33,7 @@ class ViewHighlighter { guard let center = view.superview?.convert(view.center, to: nil) else { return } let size = max(view.frame.width, view.frame.height) * 5.5 - let highlightView = AnimationView(name: "view_highlight") + let highlightView = LottieAnimationView(name: "view_highlight") highlightView.frame = CGRect(x: 0, y: 0, width: size, height: size) highlightView.center = center highlightView.isUserInteractionEnabled = false From d2ce9b638d93b96cfc2ae4152929ef8fd6aa8f9b Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 2 Apr 2024 06:35:36 -0700 Subject: [PATCH 188/245] Add LazyView to help with FAQ view allocation (#2660) Task/Issue URL: https://app.asana.com/0/414235014887631/1206765939311868/f Tech Design URL: CC: Description: This PR updates the VPN FAQ row to load the web view lazily. Before this, the web view was being recreated every time the UI updated. --- DuckDuckGo.xcodeproj/project.pbxproj | 4 +++ DuckDuckGo/LazyView.swift | 32 ++++++++++++++++++++ DuckDuckGo/NetworkProtectionStatusView.swift | 2 +- 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 DuckDuckGo/LazyView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fcc93ce1c0..2240414f03 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -263,6 +263,7 @@ 4B0F3F502B9BFF2100392892 /* NetworkProtectionFAQView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */; }; 4B274F602AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */; }; 4B37E0502B928CA6009E81CA /* vpn-light-mode.json in Resources */ = {isa = PBXBuildFile; fileRef = 4B37E04F2B928CA6009E81CA /* vpn-light-mode.json */; }; + 4B412ACC2BBB3D0900A39F5E /* LazyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B412ACB2BBB3D0900A39F5E /* LazyView.swift */; }; 4B470ED6299C49800086EBDC /* AppTrackingProtectionDatabase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B470ED5299C49800086EBDC /* AppTrackingProtectionDatabase.swift */; }; 4B470ED9299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 4B470ED7299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodeld */; }; 4B470EDB299C4FB20086EBDC /* AppTrackingProtectionListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B470EDA299C4FB20086EBDC /* AppTrackingProtectionListViewModel.swift */; }; @@ -1390,6 +1391,7 @@ 4B0F3F4F2B9BFF2100392892 /* NetworkProtectionFAQView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionFAQView.swift; sourceTree = ""; }; 4B274F5F2AFEAECC003F0745 /* NetworkProtectionWidgetRefreshModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkProtectionWidgetRefreshModel.swift; sourceTree = ""; }; 4B37E04F2B928CA6009E81CA /* vpn-light-mode.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "vpn-light-mode.json"; sourceTree = ""; }; + 4B412ACB2BBB3D0900A39F5E /* LazyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LazyView.swift; sourceTree = ""; }; 4B470ED5299C49800086EBDC /* AppTrackingProtectionDatabase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionDatabase.swift; sourceTree = ""; }; 4B470ED8299C4AED0086EBDC /* AppTrackingProtectionModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AppTrackingProtectionModel.xcdatamodel; sourceTree = ""; }; 4B470EDA299C4FB20086EBDC /* AppTrackingProtectionListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionListViewModel.swift; sourceTree = ""; }; @@ -5546,6 +5548,7 @@ 4BBBBA912B03291700D965DA /* VPNWaitlistUserText.swift */, 986DA94924884B18004A7E39 /* WebViewTransition.swift */, EE9D68D72AE15AD600B55EF4 /* UIApplicationExtension.swift */, + 4B412ACB2BBB3D0900A39F5E /* LazyView.swift */, ); name = UserInterface; sourceTree = ""; @@ -6794,6 +6797,7 @@ 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */, 8C4724502217A14B004C9B2D /* TabViewControllerLongPressBookmarkExtension.swift in Sources */, 1EDE39D22705D4A200C99C72 /* FileSizeDebugViewController.swift in Sources */, + 4B412ACC2BBB3D0900A39F5E /* LazyView.swift in Sources */, 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */, 85DDE0402AC6FF65006ABCA2 /* MainView.swift in Sources */, 980891A72237D5D800313A70 /* FeedbackPresenter.swift in Sources */, diff --git a/DuckDuckGo/LazyView.swift b/DuckDuckGo/LazyView.swift new file mode 100644 index 0000000000..7605f98e3d --- /dev/null +++ b/DuckDuckGo/LazyView.swift @@ -0,0 +1,32 @@ +// +// LazyView.swift +// DuckDuckGo +// +// 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 SwiftUI + +// https://gist.github.com/chriseidhof/d2fcafb53843df343fe07f3c0dac41d5 +struct LazyView: View { + let build: () -> Content + init(_ build: @autoclosure @escaping () -> Content) { + self.build = build + } + var body: Content { + build() + } +} diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index 2705b9fdc6..953881fd00 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -199,7 +199,7 @@ struct NetworkProtectionStatusView: View { private func about() -> some View { Section { if statusModel.shouldShowFAQ { - NavigationLink(UserText.netPVPNSettingsFAQ, destination: NetworkProtectionFAQView()) + NavigationLink(UserText.netPVPNSettingsFAQ, destination: LazyView(NetworkProtectionFAQView())) .daxBodyRegular() .foregroundColor(.init(designSystemColor: .textPrimary)) } From 82af9c2166f7faff1a24b3ccb6db07647335b5c7 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 2 Apr 2024 18:50:10 +0200 Subject: [PATCH 189/245] Subscriptions: 27: Update navigation to use Push Views (#2659) Task/Issue URL: https://app.asana.com/0/414235014887631/1206942263287858/f Description: Updates Subscription navigation to use "Push" transitions instead of sheets Adds .onFirstAppear() modifier to view as SwiftUI's .onAppear() might be called multiple times Cache subscription in memory to prevent UI Glitches Other minor fixes and renames --- Core/PixelEvent.swift | 2 + DuckDuckGo.xcodeproj/project.pbxproj | 20 +- DuckDuckGo/SettingsSubscriptionView.swift | 138 +++++--------- DuckDuckGo/SettingsView.swift | 11 +- DuckDuckGo/SettingsViewModel.swift | 39 ++-- .../Extensions/View+AppearModifiers.swift | 70 +++++++ .../Extensions/View+TopMostController.swift | 40 ---- .../SubscriptionEmailViewModel.swift | 112 +++++------ .../SubscriptionExternalLinkViewModel.swift | 2 +- .../ViewModel/SubscriptionFlowViewModel.swift | 101 +++++----- .../ViewModel/SubscriptionITPViewModel.swift | 25 +-- .../ViewModel/SubscriptionPIRViewModel.swift | 4 +- .../SubscriptionRestoreViewModel.swift | 64 ++++--- .../SubscriptionSettingsViewModel.swift | 43 +++-- .../Views/RootPresentationMode.swift | 45 ----- .../Views/SubscriptionContainerView.swift | 62 +++++++ .../Views/SubscriptionEmailView.swift | 111 ++++++----- .../Views/SubscriptionExternalLinkView.swift | 6 +- .../Views/SubscriptionFlowView.swift | 136 +++++++------- .../Views/SubscriptionITPView.swift | 73 +++----- .../SubscriptionNavigationCoordinator.swift | 25 +++ .../Views/SubscriptionPIRView.swift | 40 ++-- .../Views/SubscriptionRestoreView.swift | 175 +++++++++--------- .../Views/SubscriptionSettingsView.swift | 82 ++++---- 24 files changed, 725 insertions(+), 701 deletions(-) create mode 100644 DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift delete mode 100644 DuckDuckGo/Subscription/Extensions/View+TopMostController.swift delete mode 100644 DuckDuckGo/Subscription/Views/RootPresentationMode.swift create mode 100644 DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift create mode 100644 DuckDuckGo/Subscription/Views/SubscriptionNavigationCoordinator.swift diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 4db2056522..ba826f3a0a 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -578,6 +578,7 @@ extension Pixel { case privacyProPurchaseFailureAccountNotCreated case privacyProPurchaseSuccess case privacyProRestorePurchaseOfferPageEntry + case privacyProRestorePurchaseClick case privacyProRestorePurchaseEmailStart case privacyProRestorePurchaseStoreStart case privacyProRestorePurchaseEmailSuccess @@ -1164,6 +1165,7 @@ extension Pixel.Event { case .privacyProPurchaseFailureBackendError: return "m_privacy-pro_app_subscription-purchase_failure_account-creation" case .privacyProPurchaseSuccess: return "m_privacy-pro_app_subscription-purchase_success" case .privacyProRestorePurchaseOfferPageEntry: return "m_privacy-pro_offer_restore-purchase_click" + case .privacyProRestorePurchaseClick: return "m_privacy-pro_app-settings_restore-purchase_click" case .privacyProRestorePurchaseEmailStart: return "m_privacy-pro_activate-subscription_enter-email_click" case .privacyProRestorePurchaseStoreStart: return "m_privacy-pro_activate-subscription_restore-purchase_click" case .privacyProRestorePurchaseEmailSuccess: return "m_privacy-pro_app_subscription-restore-using-email_success" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2240414f03..8bf3c09b35 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -820,15 +820,16 @@ D668D9272B6937D2008E2FF2 /* SubscriptionITPViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */; }; D668D9292B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */; }; D668D92B2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift in Sources */ = {isa = PBXBuildFile; fileRef = D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */; }; + D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */; }; + D670E5BB2BB6A75300941A42 /* SubscriptionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */; }; + D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; - D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; - D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; D6E0C1832B7A2B1E00D5E1E9 /* DesktopDownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */; }; D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */; }; @@ -2493,15 +2494,16 @@ D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionITPViewModel.swift; sourceTree = ""; }; D668D9282B69681C008E2FF2 /* IdentityTheftRestorationPagesUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesUserScript.swift; sourceTree = ""; }; D668D92A2B696840008E2FF2 /* IdentityTheftRestorationPagesFeature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IdentityTheftRestorationPagesFeature.swift; sourceTree = ""; }; + D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerView.swift; sourceTree = ""; }; + D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionNavigationCoordinator.swift; sourceTree = ""; }; + D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AppearModifiers.swift"; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; - D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+TopMostController.swift"; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; - D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootPresentationMode.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; D6E0C1822B7A2B1E00D5E1E9 /* DesktopDownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadView.swift; sourceTree = ""; }; D6E0C1842B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DesktopDownloadPlatformConstants.swift; sourceTree = ""; }; @@ -4656,7 +4658,7 @@ isa = PBXGroup; children = ( D664C7982B289AA000CBFA76 /* WKUserContentController+Handler.swift */, - D69DBB4F2B72B1D200156310 /* View+TopMostController.swift */, + D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */, ); path = Extensions; sourceTree = ""; @@ -4665,6 +4667,7 @@ isa = PBXGroup; children = ( D664C7AD2B289AA000CBFA76 /* PurchaseInProgressView.swift */, + D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */, D664C7AE2B289AA000CBFA76 /* SubscriptionFlowView.swift */, D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */, D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */, @@ -4672,8 +4675,8 @@ D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */, D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */, D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, - D6D95CE02B6D52DA00960317 /* RootPresentationMode.swift */, D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */, + D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */, ); path = Views; sourceTree = ""; @@ -6646,6 +6649,7 @@ 020108A929A7C1CD00644F9D /* AppTrackerImageCache.swift in Sources */, 4B78074E2B183A1F009DB2CF /* SurveyURLBuilder.swift in Sources */, 3132FA2A27A0788F00DD7A12 /* QuickLookPreviewHelper.swift in Sources */, + D670E5BB2BB6A75300941A42 /* SubscriptionNavigationCoordinator.swift in Sources */, C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, @@ -6752,6 +6756,7 @@ B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */, 981FED6E22025151008488D7 /* BlankSnapshotViewController.swift in Sources */, 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */, + D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */, 851B128822200575004781BC /* Onboarding.swift in Sources */, 3151F0EE2735800800226F58 /* VoiceSearchFeedbackView.swift in Sources */, 857EEB752095FFAC008A005C /* HomeRowInstructionsViewController.swift in Sources */, @@ -6783,7 +6788,6 @@ 31C70B5528045E3500FB6AD1 /* SecureVaultErrorReporter.swift in Sources */, F4CE6D1B257EA33C00D0A6AA /* FireButtonAnimator.swift in Sources */, 85582E0029D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift in Sources */, - D69DBB502B72B1D300156310 /* View+TopMostController.swift in Sources */, EE0153EF2A70021E002A8B26 /* NetworkProtectionInviteView.swift in Sources */, 9888F77B2224980500C46159 /* FeedbackViewController.swift in Sources */, D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */, @@ -6913,6 +6917,7 @@ 984D035C24AE15CD0066CFB8 /* TabSwitcherSettings.swift in Sources */, D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, + D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */, C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, @@ -6970,7 +6975,6 @@ D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, 1E4FAA6627D8DFC800ADC5B3 /* CompleteDownloadRowViewModel.swift in Sources */, - D6D95CE12B6D52DA00960317 /* RootPresentationMode.swift in Sources */, 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */, EE01EB402AFBD0000096AAC9 /* NetworkProtectionVPNSettingsViewModel.swift in Sources */, EE72CA852A862D000043B5B3 /* NetworkProtectionDebugViewController.swift in Sources */, diff --git a/DuckDuckGo/SettingsSubscriptionView.swift b/DuckDuckGo/SettingsSubscriptionView.swift index d032e18e81..df5b603b87 100644 --- a/DuckDuckGo/SettingsSubscriptionView.swift +++ b/DuckDuckGo/SettingsSubscriptionView.swift @@ -22,16 +22,16 @@ import UIKit #if SUBSCRIPTION import Subscription +import Core @available(iOS 15.0, *) struct SettingsSubscriptionView: View { @EnvironmentObject var viewModel: SettingsViewModel - @StateObject var subscriptionFlowViewModel = SubscriptionFlowViewModel() - @StateObject var subscriptionRestoreViewModel = SubscriptionRestoreViewModel() - @State var isShowingSubscriptionFlow = false - @State var isShowingSubscriptionRestoreFlow = false + @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator @State var isShowingDBP = false @State var isShowingITP = false + @State var isShowingRestoreFlow = false + @State var isShowingSubscribeFlow = false enum Constants { static let purchaseDescriptionPadding = 5.0 @@ -51,18 +51,6 @@ struct SettingsSubscriptionView: View { } } - private var learnMoreView: some View { - Text(UserText.settingsPProLearnMore) - .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent)) - } - - private var iHaveASubscriptionView: some View { - Text(UserText.settingsPProIHaveASubscription) - .daxBodyRegular() - .foregroundColor(Color.init(designSystemColor: .accent)) - } - @ViewBuilder private var restorePurchaseView: some View { let text = !viewModel.isRestoringSubscription ? UserText.subscriptionActivateAppleIDButton : UserText.subscriptionRestoringTitle @@ -90,31 +78,27 @@ struct SettingsSubscriptionView: View { @ViewBuilder private var purchaseSubscriptionView: some View { + Group { SettingsCustomCell(content: { subscriptionDescriptionView }) - SettingsCustomCell(content: { learnMoreView }, - action: { isShowingSubscriptionFlow = true }, - isButton: true ) - // Subscription Purchase - .sheet(isPresented: $isShowingSubscriptionFlow, - onDismiss: { Task { viewModel.onAppear() } }, - content: { - SubscriptionFlowView(viewModel: subscriptionFlowViewModel).interactiveDismissDisabled() - }) - - SettingsCustomCell(content: { iHaveASubscriptionView }, - action: { - isShowingSubscriptionRestoreFlow = true - }, - isButton: true ) + let subscribeView = SubscriptionContainerView(currentView: .subscribe) + .navigationViewStyle(.stack) + .environmentObject(subscriptionNavigationCoordinator) + let restoreView = SubscriptionContainerView(currentView: .restore) + .navigationViewStyle(.stack) + .environmentObject(subscriptionNavigationCoordinator) + .onFirstAppear { + Pixel.fire(pixel: .privacyProRestorePurchaseClick) + } + + NavigationLink(destination: subscribeView, + isActive: $isShowingSubscribeFlow, + label: { SettingsCellView(label: UserText.settingsPProLearnMore ) }) - // Subscription Restore - .sheet(isPresented: $isShowingSubscriptionRestoreFlow, - onDismiss: { Task { viewModel.onAppear() } }, - content: { - SubscriptionRestoreView(viewModel: subscriptionRestoreViewModel).interactiveDismissDisabled() - }) + NavigationLink(destination: restoreView, + isActive: $isShowingRestoreFlow, + label: { SettingsCellView(label: UserText.settingsPProIHaveASubscription ) }) } } @@ -139,7 +123,7 @@ struct SettingsSubscriptionView: View { @ViewBuilder private var subscriptionDetailsView: some View { - Group { + if viewModel.shouldShowNetP { SettingsCellView(label: UserText.settingsPProVPNTitle, subtitle: viewModel.state.networkProtection.status != "" ? viewModel.state.networkProtection.status : nil, @@ -149,31 +133,27 @@ struct SettingsSubscriptionView: View { } if viewModel.shouldShowDBP { - SettingsCellView(label: UserText.settingsPProDBPTitle, - subtitle: UserText.settingsPProDBPSubTitle, - action: { isShowingDBP.toggle() }, isButton: true) - - .sheet(isPresented: $isShowingDBP) { - SubscriptionPIRView() - } + NavigationLink(destination: SubscriptionPIRView(), + isActive: $isShowingDBP, + label: { + SettingsCellView(label: UserText.settingsPProDBPTitle, + subtitle: UserText.settingsPProDBPSubTitle) + }) } - + if viewModel.shouldShowITP { - SettingsCellView(label: UserText.settingsPProITRTitle, - subtitle: UserText.settingsPProITRSubTitle, - action: { isShowingITP.toggle() }, isButton: true) - - .sheet(isPresented: $isShowingITP) { - SubscriptionITPView() - } + NavigationLink(destination: SubscriptionITPView(), + isActive: $isShowingITP, + label: { + SettingsCellView(label: UserText.settingsPProITRTitle, + subtitle: UserText.settingsPProITRSubTitle) + }) } - NavigationLink(destination: SubscriptionSettingsView()) { + NavigationLink(destination: SubscriptionSettingsView().environmentObject(subscriptionNavigationCoordinator)) { SettingsCustomCell(content: { manageSubscriptionView }) - } - } } @@ -182,50 +162,30 @@ struct SettingsSubscriptionView: View { if viewModel.state.subscription.enabled && viewModel.state.subscription.canPurchase { Section(header: Text(UserText.settingsPProSection)) { if viewModel.state.subscription.hasActiveSubscription { - - if !viewModel.isLoadingSubscriptionState { - // Allow managing the subscription if we have some entitlements - if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { - subscriptionDetailsView - - // If no entitlements it should mean the backend is still out of sync - } else { - noEntitlementsAvailableView - } + // Allow managing the subscription if we have some entitlements + if viewModel.shouldShowDBP || viewModel.shouldShowITP || viewModel.shouldShowNetP { + subscriptionDetailsView + + // If no entitlements it should mean the backend is still out of sync + } else { + noEntitlementsAvailableView } + } else if viewModel.state.subscription.isSubscriptionPendingActivation { noEntitlementsAvailableView } else { purchaseSubscriptionView } } - - // Selected Feature handler for Subscription Flow - .onChange(of: subscriptionFlowViewModel.selectedFeature) { value in - guard let value else { return } - viewModel.triggerDeepLinkNavigation(to: value) - } - - // Selected Feature handler for Subscription Restore - .onChange(of: subscriptionRestoreViewModel.emailViewModel.selectedFeature) { value in - guard let value else { return } - viewModel.triggerDeepLinkNavigation(to: value) - } - // Selected Feature handler for SubscriptionActivation - .onChange(of: subscriptionFlowViewModel.state.shouldActivateSubscription) { value in - if value { - viewModel.triggerDeepLinkNavigation(to: .subscriptionRestoreFlow) + .onReceive(subscriptionNavigationCoordinator.$shouldPopToAppSettings) { shouldDismiss in + if shouldDismiss { + isShowingRestoreFlow = false + isShowingSubscribeFlow = false } } - - // Selected Feature handler for Show Plans - .onChange(of: subscriptionRestoreViewModel.state.shouldShowPlans) { value in - if value { - viewModel.triggerDeepLinkNavigation(to: .subscriptionFlow) - } - } + } } } diff --git a/DuckDuckGo/SettingsView.swift b/DuckDuckGo/SettingsView.swift index d3f1a90a99..5d4d6d11db 100644 --- a/DuckDuckGo/SettingsView.swift +++ b/DuckDuckGo/SettingsView.swift @@ -21,11 +21,12 @@ import SwiftUI import UIKit import DesignResourcesKit + struct SettingsView: View { @StateObject var viewModel: SettingsViewModel @Environment(\.presentationMode) var presentationMode - + @State private var subscriptionNavigationCoordinator = SubscriptionNavigationCoordinator() @State private var shouldDisplayDeepLinkSheet: Bool = false @State private var shouldDisplayDeepLinkPush: Bool = false #if SUBSCRIPTION @@ -56,7 +57,7 @@ struct SettingsView: View { SettingsPrivacyView() #if SUBSCRIPTION if #available(iOS 15, *) { - SettingsSubscriptionView() + SettingsSubscriptionView().environmentObject(subscriptionNavigationCoordinator) } #endif SettingsCustomizeView() @@ -109,7 +110,7 @@ struct SettingsView: View { DispatchQueue.main.async { self.shouldDisplayDeepLinkSheet = true } - case .navigation: + case .navigationLink: DispatchQueue.main.async { self.shouldDisplayDeepLinkPush = true } @@ -133,9 +134,9 @@ struct SettingsView: View { case .itr: SubscriptionITPView() case .subscriptionFlow: - SubscriptionFlowView() + SubscriptionContainerView(currentView: .subscribe).environmentObject(subscriptionNavigationCoordinator) case .subscriptionRestoreFlow: - SubscriptionRestoreView() + SubscriptionContainerView(currentView: .restore).environmentObject(subscriptionNavigationCoordinator) default: EmptyView() } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 2f877dd1a9..2ef9137929 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -55,11 +55,15 @@ final class SettingsViewModel: ObservableObject { private var isPrivacyProEnabled: Bool { AppDependencyProvider.shared.subscriptionFeatureAvailability.isFeatureAvailable } + // Cache subscription state in memory to prevent UI glitches + private var cacheSubscriptionState: SettingsState.Subscription = SettingsState.Subscription(enabled: false, + canPurchase: false, + hasActiveSubscription: false, + isSubscriptionPendingActivation: false) // Sheet Presentation & Navigation @Published var isRestoringSubscription: Bool = false @Published var shouldDisplayRestoreSubscriptionError: Bool = false - @Published var isLoadingSubscriptionState: Bool = false @Published var shouldShowNetP = false @Published var shouldShowDBP = false @Published var shouldShowITP = false @@ -266,8 +270,8 @@ extension SettingsViewModel { // other dependencies are observable (Such as AppIcon and netP) // and we can use subscribers (Currently called from the view onAppear) @MainActor - private func initState() async { - self.state = await SettingsState( + private func initState() { + self.state = SettingsState( appTheme: appSettings.currentThemeName, appIcon: AppIconManager.shared.appIcon, fireButtonAnimation: appSettings.currentFireButtonAnimation, @@ -288,11 +292,12 @@ extension SettingsViewModel { speechRecognitionAvailable: AppDependencyProvider.shared.voiceSearchHelper.isSpeechRecognizerAvailable, loginsEnabled: featureFlagger.isFeatureOn(.autofillAccessCredentialManagement), networkProtection: getNetworkProtectionState(), - subscription: getSubscriptionState(), + subscription: cacheSubscriptionState, sync: getSyncState() ) setupSubscribers() + Task { await refreshSubscriptionState() } } @@ -305,6 +310,13 @@ extension SettingsViewModel { #endif return SettingsState.NetworkProtection(enabled: enabled, status: "") } + + private func refreshSubscriptionState() async { + let state = await self.getSubscriptionState() + DispatchQueue.main.async { + self.state.subscription = state + } + } private func getSubscriptionState() async -> SettingsState.Subscription { var enabled = false @@ -322,6 +334,12 @@ extension SettingsViewModel { switch subscriptionResult { case .success(let subscription): hasActiveSubscription = subscription.isActive + + cacheSubscriptionState = SettingsState.Subscription(enabled: enabled, + canPurchase: canPurchase, + hasActiveSubscription: hasActiveSubscription, + isSubscriptionPendingActivation: isSubscriptionPendingActivation) + case .failure: if await PurchaseManager.hasActiveSubscription() { isSubscriptionPendingActivation = true @@ -376,8 +394,7 @@ extension SettingsViewModel { setupSubscriptionPurchaseOptions() return } - - isLoadingSubscriptionState = true + // Fetch available subscriptions from the backend (or sign out) switch await SubscriptionService.getSubscription(accessToken: token) { @@ -418,7 +435,7 @@ extension SettingsViewModel { signOutUser() } } - isLoadingSubscriptionState = false + } @available(iOS 15.0, *) @@ -440,7 +457,7 @@ extension SettingsViewModel { signOutObserver = NotificationCenter.default.addObserver(forName: .accountDidSignOut, object: nil, queue: .main) { [weak self] _ in if #available(iOS 15.0, *) { guard let strongSelf = self else { return } - Task { await strongSelf.setupSubscriptionEnvironment() } + Task { await strongSelf.refreshSubscriptionState() } } } } @@ -657,11 +674,11 @@ extension SettingsViewModel { // Specify cases that require .push presentation // Example: // case .dbp: - // return .push + // return .sheet case .netP: return .UIKitView default: - return .sheet + return .navigationLink } } } @@ -669,7 +686,7 @@ extension SettingsViewModel { // Define DeepLinkType outside the enum if not already defined enum DeepLinkType { case sheet - case navigation + case navigationLink case UIKitView } diff --git a/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift b/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift new file mode 100644 index 0000000000..df8ce7bda2 --- /dev/null +++ b/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift @@ -0,0 +1,70 @@ +// +// View+AppearModifiers.swift +// DuckDuckGo +// +// 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 + +public struct OnFirstAppearModifier: ViewModifier { + + private let onFirstAppearAction: () -> Void + @State private var hasAppeared = false + + public init(_ onFirstAppearAction: @escaping () -> Void) { + self.onFirstAppearAction = onFirstAppearAction + } + + public func body(content: Content) -> some View { + content + .onAppear { + guard !hasAppeared else { return } + hasAppeared = true + onFirstAppearAction() + } + } +} + +public struct OnFirstDisappearModifier: ViewModifier { + + private let onFirstDisappearAction: () -> Void + @State private var hasDisappeared = false + + public init(_ onFirstDisappearAction: @escaping () -> Void) { + self.onFirstDisappearAction = onFirstDisappearAction + } + + public func body(content: Content) -> some View { + content + .onDisappear { + guard !hasDisappeared else { return } + hasDisappeared = true + onFirstDisappearAction() + } + } +} + +extension View { + + func onFirstAppear(_ onFirstAppearAction: @escaping () -> Void ) -> some View { + return modifier(OnFirstAppearModifier(onFirstAppearAction)) + } + + func onFirstDisappear(_ onFirstDisappearAction: @escaping () -> Void ) -> some View { + return modifier(OnFirstDisappearModifier(onFirstDisappearAction)) + } + +} diff --git a/DuckDuckGo/Subscription/Extensions/View+TopMostController.swift b/DuckDuckGo/Subscription/Extensions/View+TopMostController.swift deleted file mode 100644 index 2d26949767..0000000000 --- a/DuckDuckGo/Subscription/Extensions/View+TopMostController.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// View+TopMostController.swift -// DuckDuckGo -// -// 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 - -extension View { - - // Grabs the topMost controller so we can properly present sheets anywhere - func topMostViewController() -> UIViewController? { - guard let keyWindow = UIApplication.shared.connectedScenes - .filter({ $0.activationState == .foregroundActive }) - .compactMap({ $0 as? UIWindowScene }) - .first?.windows - .filter({ $0.isKeyWindow }).first else { - return nil - } - - var topController = keyWindow.rootViewController - while let presentedController = topController?.presentedViewController { - topController = presentedController - } - return topController - } -} diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 8b8f283f11..dc46e7e153 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -37,24 +37,28 @@ final class SubscriptionEmailViewModel: ObservableObject { var viewTitle = UserText.subscriptionActivateEmailTitle var webViewModel: AsyncHeadlessWebViewViewModel + enum SelectedFeature { + case netP, dbp, itr, none + } + struct State { var subscriptionEmail: String? var managingSubscriptionEmail = false var transactionError: SubscriptionRestoreError? var shouldDisplaynavigationError: Bool = false - var shouldDisplayInactiveError: Bool = false + var isPresentingInactiveError: Bool = false var canNavigateBack: Bool = false var shouldDismissView: Bool = false - var shouldDismissStack: Bool = false var subscriptionActive: Bool = false + var backButtonTitle: String = UserText.backButtonTitle + var selectedFeature: SelectedFeature = .none + var shouldPopToSubscriptionSettings: Bool = false + var shouldPopToAppSettings: Bool = false } // Read only View State - Should only be modified from the VM @Published private(set) var state = State() - // Publish the currently selected feature - @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? - private static let allowedDomains = [ "duckduckgo.com" ] enum SubscriptionRestoreError: Error { @@ -65,8 +69,8 @@ final class SubscriptionEmailViewModel: ObservableObject { private var cancellables = Set() - init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), - subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), + init(userScript: SubscriptionPagesUserScript, + subFeature: SubscriptionPagesUseSubscriptionFeature, accountManager: AccountManager = AccountManager()) { self.userScript = userScript self.subFeature = subFeature @@ -76,11 +80,6 @@ final class SubscriptionEmailViewModel: ObservableObject { settings: AsyncHeadlessWebViewSettings(bounces: false, allowedDomains: Self.allowedDomains, contentBlocking: false)) - - Task { - await initializeView() - } - setupObservers() } @MainActor @@ -88,7 +87,13 @@ final class SubscriptionEmailViewModel: ObservableObject { if state.canNavigateBack { await webViewModel.navigationCoordinator.goBack() } else { - state.shouldDismissView = true + // If not in the Welcome page, dismiss the view, otherwise, assume we + // came from Activation, so dismiss the entire stack + if webViewModel.url?.forComparison() != URL.subscriptionPurchase.forComparison() { + state.shouldDismissView = true + } else { + state.shouldPopToAppSettings = true + } } } @@ -96,19 +101,9 @@ final class SubscriptionEmailViewModel: ObservableObject { state.shouldDismissView = false } - func onAppear() { - Task { await initializeView() } - Task { await setupSubscribers() } - webViewModel.navigationCoordinator.navigateTo(url: emailURL ) - } - - func onDissappear() { - cancellables.removeAll() - canGoBackCancellable = nil - } - @MainActor - private func initializeView() { + func onFirstAppear() { + setupObservers() if accountManager.isUserAuthenticated { // If user is authenticated, we want to "Add or manage email" instead of activating emailURL = accountManager.email == nil ? URL.addEmailToSubscription : URL.manageSubscriptionEmail @@ -117,20 +112,25 @@ final class SubscriptionEmailViewModel: ObservableObject { // Also we assume subscription requires managing, and not activation state.managingSubscriptionEmail = true } + if webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess { + self.webViewModel.navigationCoordinator.navigateTo(url: self.emailURL) + } } - private func setupSubscribers() async { + func onFirstDisappear() { + cancellables.removeAll() + canGoBackCancellable = nil + } + + private func setupObservers() { + + // Webview navigation canGoBackCancellable = webViewModel.$canGoBack .receive(on: DispatchQueue.main) .sink { [weak self] value in - self?.state.canNavigateBack = false - if self?.webViewModel.url != URL.activateSubscriptionViaEmail.forComparison() { - self?.state.canNavigateBack = value - } + self?.updateBackButton(canNavigateBack: value) } - } - - private func setupObservers() { + // Feature Callback subFeature.onSetSubscription = { DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailSuccess) @@ -138,10 +138,11 @@ final class SubscriptionEmailViewModel: ObservableObject { DispatchQueue.main.async { self.state.subscriptionActive = true } + self.dismissStack() } subFeature.onBackToSettings = { - self.dismissView() + self.dismissStack() } subFeature.onFeatureSelected = { feature in @@ -149,19 +150,13 @@ final class SubscriptionEmailViewModel: ObservableObject { switch feature { case .netP: UniquePixel.fire(pixel: .privacyProWelcomeVPN) - self.selectedFeature = .netP + self.state.selectedFeature = .netP case .itr: UniquePixel.fire(pixel: .privacyProWelcomePersonalInformationRemoval) - self.selectedFeature = .itr + self.state.selectedFeature = .itr case .dbp: UniquePixel.fire(pixel: .privacyProWelcomeIdentityRestoration) - self.selectedFeature = .dbp - } - self.state.shouldDismissStack = true - - // Reset shouldDismissStack after dismissal to ensure it can be triggered again - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - self.state.shouldDismissStack = false + self.state.selectedFeature = .dbp } } @@ -177,7 +172,7 @@ final class SubscriptionEmailViewModel: ObservableObject { } } .store(in: &cancellables) - + webViewModel.$navigationError .receive(on: DispatchQueue.main) .sink { [weak self] error in @@ -190,14 +185,21 @@ final class SubscriptionEmailViewModel: ObservableObject { .store(in: &cancellables) } - func shouldDisplayBackButton() -> Bool { - // Hide the back button after activation - if state.subscriptionActive && - (webViewModel.url == URL.subscriptionActivateSuccess.forComparison() || - webViewModel.url == URL.subscriptionPurchase.forComparison()) { - return false + func updateBackButton(canNavigateBack: Bool) { + + // Disable Browser navigation by default + self.state.canNavigateBack = false + + // If the view is not Activation Success, or Welcome page, allow WebView Back Navigation + if self.webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess.forComparison() && + self.webViewModel.url?.forComparison() != URL.subscriptionPurchase.forComparison() { + self.state.canNavigateBack = canNavigateBack + self.state.backButtonTitle = UserText.backButtonTitle + } else { + self.state.backButtonTitle = UserText.settingsTitle } - return true + + } // MARK: - @@ -210,7 +212,7 @@ final class SubscriptionEmailViewModel: ObservableObject { default: state.transactionError = .generalError } - state.shouldDisplayInactiveError = true + state.isPresentingInactiveError = true } func dismissView() { @@ -219,6 +221,12 @@ final class SubscriptionEmailViewModel: ObservableObject { } } + func dismissStack() { + DispatchQueue.main.async { + self.state.shouldPopToSubscriptionSettings = true + } + } + deinit { cancellables.removeAll() canGoBackCancellable = nil diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift index fd1c2557a7..f2f5feb45d 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift @@ -53,7 +53,7 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { } } - func initializeView() { + func onFirstAppear() { Task { await setupSubscribers() } webViewModel.navigationCoordinator.navigateTo(url: url) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 8c61b4f8cb..518a9cd0e9 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -43,20 +43,20 @@ final class SubscriptionFlowViewModel: ObservableObject { static let navigationBarHideThreshold = 80.0 } - + enum SelectedFeature { + case netP, dbp, itr, none + } + struct State { var hasActiveSubscription = false var transactionStatus: SubscriptionTransactionStatus = .idle var userTappedRestoreButton = false - var shouldDismissView = false var shouldActivateSubscription = false - var shouldShowNavigationBar: Bool = false var canNavigateBack: Bool = false var transactionError: SubscriptionPurchaseError? + var shouldHideBackButton = false + var selectedFeature: SelectedFeature = .none } - - // Publish the currently selected feature - @Published var selectedFeature: SettingsViewModel.SettingsDeepLinkSection? // Read only View State - Should only be modified from the VM @Published private(set) var state = State() @@ -67,14 +67,13 @@ final class SubscriptionFlowViewModel: ObservableObject { allowedDomains: allowedDomains, contentBlocking: false) - init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), - subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), + init(userScript: SubscriptionPagesUserScript, + subFeature: SubscriptionPagesUseSubscriptionFeature, purchaseManager: PurchaseManager = PurchaseManager.shared, selectedFeature: SettingsViewModel.SettingsDeepLinkSection? = nil) { self.userScript = userScript self.subFeature = subFeature self.purchaseManager = purchaseManager - self.selectedFeature = selectedFeature self.webViewModel = AsyncHeadlessWebViewViewModel(userScript: userScript, subFeature: subFeature, settings: webViewSettings) @@ -100,9 +99,7 @@ final class SubscriptionFlowViewModel: ObservableObject { subFeature.onActivateSubscription = { DispatchQueue.main.async { - self.state.shouldDismissView = true self.state.shouldActivateSubscription = true - } } @@ -111,15 +108,14 @@ final class SubscriptionFlowViewModel: ObservableObject { switch feature { case .netP: UniquePixel.fire(pixel: .privacyProWelcomeVPN) - self.selectedFeature = .netP - case .itr: - UniquePixel.fire(pixel: .privacyProWelcomePersonalInformationRemoval) - self.selectedFeature = .itr + self.state.selectedFeature = .netP case .dbp: + UniquePixel.fire(pixel: .privacyProWelcomePersonalInformationRemoval) + self.state.selectedFeature = .dbp + case .itr: UniquePixel.fire(pixel: .privacyProWelcomeIdentityRestoration) - self.selectedFeature = .dbp + self.state.selectedFeature = .itr } - self.state.shouldDismissView = true } } @@ -149,7 +145,6 @@ final class SubscriptionFlowViewModel: ObservableObject { state.transactionError = .purchaseFailed case .missingEntitlements: isBackendError = true - state.shouldDismissView = true state.transactionError = .missingEntitlements case .failedToGetSubscriptionOptions: isStoreError = true @@ -199,16 +194,6 @@ final class SubscriptionFlowViewModel: ObservableObject { // swiftlint:enable cyclomatic_complexity private func setupWebViewObservers() async { - webViewModel.$scrollPosition - .receive(on: DispatchQueue.main) - .sink { [weak self] value in - guard let strongSelf = self else { return } - DispatchQueue.main.async { - strongSelf.state.shouldShowNavigationBar = value.y > Constants.navigationBarHideThreshold - } - } - .store(in: &cancellables) - webViewModel.$navigationError .receive(on: DispatchQueue.main) .sink { [weak self] error in @@ -235,9 +220,25 @@ final class SubscriptionFlowViewModel: ObservableObject { } private func backButtonForURL(currentURL: URL) -> Bool { - return currentURL != URL.subscriptionBaseURL.forComparison() && - currentURL != URL.subscriptionActivateSuccess.forComparison() && - currentURL != URL.subscriptionPurchase.forComparison() + print(currentURL) + return currentURL.forComparison() != URL.subscriptionBaseURL.forComparison() && + currentURL.forComparison() != URL.subscriptionActivateSuccess.forComparison() && + currentURL.forComparison() != URL.subscriptionPurchase.forComparison() + } + + private func cleanUp() { + canGoBackCancellable?.cancel() + subFeature.cleanup() + cancellables.removeAll() + } + + @MainActor + func resetState() { + self.state = State() + } + + deinit { + cleanUp() } @MainActor @@ -249,41 +250,23 @@ final class SubscriptionFlowViewModel: ObservableObject { private func backButtonEnabled(_ enabled: Bool) { state.canNavigateBack = enabled } + + // MARK: - - @MainActor - func onAppear() async { + func onFirstAppear() async { DispatchQueue.main.async { self.resetState() - self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL ) } - Pixel.fire(pixel: .privacyProOfferScreenImpression, debounce: 2) await self.setupTransactionObserver() await self .setupWebViewObservers() - } - - func onDisappear() { - DispatchQueue.main.async { - self.resetState() + if webViewModel.url == nil { + self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL) } - canGoBackCancellable?.cancel() - cancellables.removeAll() - } - - @MainActor - private func resetState() { - self.state = State() + Pixel.fire(pixel: .privacyProOfferScreenImpression) } - - @MainActor - func finalizeSubscriptionFlow() { - self.state.shouldDismissView = true - } - - deinit { - canGoBackCancellable?.cancel() - selectedFeature = nil - subFeature.cleanup() - cancellables.removeAll() + + func onFirstDisappear() async { + cleanUp() } @MainActor @@ -312,6 +295,6 @@ final class SubscriptionFlowViewModel: ObservableObject { func clearTransactionError() { state.transactionError = nil } - + } #endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 99694a5468..6efa74f56d 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -30,10 +30,9 @@ final class SubscriptionITPViewModel: ObservableObject { var userScript: IdentityTheftRestorationPagesUserScript? var subFeature: IdentityTheftRestorationPagesFeature? var manageITPURL = URL.identityTheftRestoration - var viewTitle = UserText.subscriptionTitle + var viewTitle = UserText.settingsPProITRTitle enum Constants { - static let navigationBarHideThreshold = 15.0 static let downloadableContent = ["application/pdf"] static let blankURL = "about:blank" static let externalSchemes = ["tel", "sms", "facetime"] @@ -41,7 +40,6 @@ final class SubscriptionITPViewModel: ObservableObject { // State variables var itpURL = URL.identityTheftRestoration - @Published var shouldShowNavigationBar: Bool = false @Published var canNavigateBack: Bool = false @Published var isDownloadableContent: Bool = false @Published var activityItems: [Any] = [] @@ -55,11 +53,7 @@ final class SubscriptionITPViewModel: ObservableObject { } private var currentURL: URL? - private static let allowedDomains = [ - "duckduckgo.com", - "microsoftonline.com", - "duosecurity.com", - ] + private static let allowedDomains = [ "duckduckgo.com" ] private var externalLinksViewModel: SubscriptionExternalLinkViewModel? // Limit navigation to these external domains @@ -81,8 +75,7 @@ final class SubscriptionITPViewModel: ObservableObject { subFeature: subFeature, settings: webViewSettings) } - - // swiftlint:disable function_body_length + private func setupSubscribers() async { webViewModel.$navigationError @@ -96,14 +89,6 @@ final class SubscriptionITPViewModel: ObservableObject { } .store(in: &cancellables) - webViewModel.$scrollPosition - .receive(on: DispatchQueue.main) - .throttle(for: .milliseconds(100), scheduler: DispatchQueue.main, latest: true) - .sink { [weak self] value in - self?.shouldShowNavigationBar = (value.y > Constants.navigationBarHideThreshold) - } - .store(in: &cancellables) - webViewModel.$contentType .receive(on: DispatchQueue.main) .sink { [weak self] value in @@ -146,9 +131,9 @@ final class SubscriptionITPViewModel: ObservableObject { self?.canNavigateBack = value } } - // swiftlint:enable function_body_length + - func initializeView() { + func onFirstAppear() { webViewModel.navigationCoordinator.navigateTo(url: manageITPURL ) Task { await setupSubscribers() } Pixel.fire(pixel: .privacyProIdentityRestorationSettings) diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift index 75f6cf72ea..658dc2fff4 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionPIRViewModel.swift @@ -24,9 +24,9 @@ import Core @available(iOS 15.0, *) final class SubscriptionPIRViewModel: ObservableObject { - var viewTitle = UserText.subscriptionTitle + var viewTitle = UserText.settingsPProDBPTitle - func onAppear() { + func onFirstAppear() { Pixel.fire(pixel: .privacyProPersonalInformationRemovalSettings) } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 8a1678c8f9..3a56f4cc29 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -43,14 +43,12 @@ final class SubscriptionRestoreViewModel: ObservableObject { var transactionStatus: SubscriptionTransactionStatus = .idle var activationResult: SubscriptionActivationResult = .unknown var subscriptionEmail: String? - var shouldShowWelcomePage = false - var shouldNavigateToActivationFlow = false + var isShowingWelcomePage = false + var isShowingActivationFlow = false var shouldShowPlans = false var shouldDismissView = false - - var viewTitle: String { - isAddingDevice ? UserText.subscriptionAddDeviceTitle : UserText.subscriptionActivate - } + var isLoading = false + var viewTitle: String = "" } // Publish the currently selected feature @@ -58,12 +56,9 @@ final class SubscriptionRestoreViewModel: ObservableObject { // Read only View State - Should only be modified from the VM @Published private(set) var state = State() - - // Email View Model - var emailViewModel = SubscriptionEmailViewModel() - init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), - subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature(), + init(userScript: SubscriptionPagesUserScript, + subFeature: SubscriptionPagesUseSubscriptionFeature, purchaseManager: PurchaseManager = PurchaseManager.shared, accountManager: AccountManager = AccountManager(), isAddingDevice: Bool = false) { @@ -74,36 +69,56 @@ final class SubscriptionRestoreViewModel: ObservableObject { self.state.isAddingDevice = false } - func initializeView() { - Task { await setupTransactionObserver() } + func onFirstAppear() async { + DispatchQueue.main.async { + self.resetState() + } + await setupContent() + await setupTransactionObserver() + } + + func onFirstDisappear() async { + cleanUp() } - @MainActor - func onAppear() { - resetState() - Task { + private func cleanUp() { + cancellables.removeAll() + } + + private func setupContent() async { + if state.isAddingDevice { + DispatchQueue.main.async { + self.state.isLoading = true + } + Pixel.fire(pixel: .privacyProSettingsAddDevice) guard let token = accountManager.accessToken else { return } - if case .success(let details) = await accountManager.fetchAccountDetails(with: token) { + switch await accountManager.fetchAccountDetails(with: token) { + case .success(let details): DispatchQueue.main.async { self.state.subscriptionEmail = details.email + self.state.isLoading = false + self.state.viewTitle = UserText.subscriptionAddDeviceTitle } + default: + state.isLoading = false + } + } else { + DispatchQueue.main.async { + self.state.viewTitle = UserText.subscriptionActivate } } - } @MainActor private func resetState() { - state.subscriptionEmail = accountManager.email - state.isAddingDevice = false if accountManager.isUserAuthenticated { state.isAddingDevice = true } - state.shouldNavigateToActivationFlow = false + state.isShowingActivationFlow = false state.shouldShowPlans = false - state.shouldShowWelcomePage = false + state.isShowingWelcomePage = false state.shouldDismissView = false } @@ -169,14 +184,13 @@ final class SubscriptionRestoreViewModel: ObservableObject { @MainActor func showActivationFlow(_ visible: Bool) { if visible != state.shouldDismissView { - self.state.shouldNavigateToActivationFlow = visible + self.state.isShowingActivationFlow = visible } } @MainActor func showPlans() { state.shouldShowPlans = true - state.shouldDismissView = true } @MainActor diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift index 9b306c7bf3..d7d3134d2c 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionSettingsViewModel.swift @@ -37,14 +37,14 @@ final class SubscriptionSettingsViewModel: ObservableObject { struct State { var subscriptionDetails: String = "" var subscriptionType: String = "" - var shouldDisplayRemovalNotice: Bool = false + var isShowingRemovalNotice: Bool = false var shouldDismissView: Bool = false - var shouldDisplayGoogleView: Bool = false - var shouldDisplayFAQView: Bool = false + var isShowingGoogleView: Bool = false + var isShowingFAQView: Bool = false // Used to display stripe WebUI var stripeViewModel: SubscriptionExternalLinkViewModel? - var shouldDisplayStripeView: Bool = false + var isShowingStripeView: Bool = false // Used to display the FAQ WebUI var FAQViewModel: SubscriptionExternalLinkViewModel = SubscriptionExternalLinkViewModel(url: URL.subscriptionFAQ) @@ -59,7 +59,6 @@ final class SubscriptionSettingsViewModel: ObservableObject { init(accountManager: AccountManager = AccountManager()) { self.accountManager = accountManager - Task { await fetchAndUpdateSubscriptionDetails() } setupSubscriptionUpdater() setupNotificationObservers() } @@ -70,21 +69,26 @@ final class SubscriptionSettingsViewModel: ObservableObject { return formatter }() - @MainActor - func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad) { + func onFirstAppear() { + fetchAndUpdateSubscriptionDetails() + } + + private func fetchAndUpdateSubscriptionDetails(cachePolicy: SubscriptionService.CachePolicy = .returnCacheDataElseLoad) { Task { guard let token = self.accountManager.accessToken else { return } let subscriptionResult = await SubscriptionService.getSubscription(accessToken: token, cachePolicy: cachePolicy) switch subscriptionResult { case .success(let subscription): subscriptionInfo = subscription - updateSubscriptionsStatusMessage(status: subscription.status, + await updateSubscriptionsStatusMessage(status: subscription.status, date: subscription.expiresOrRenewsAt, product: subscription.productId, billingPeriod: subscription.billingPeriod) case .failure: AccountManager().signOut() - state.shouldDismissView = true + DispatchQueue.main.async { + self.state.shouldDismissView = true + } } } } @@ -117,12 +121,11 @@ final class SubscriptionSettingsViewModel: ObservableObject { private func setupSubscriptionUpdater() { subscriptionUpdateTimer = Timer.scheduledTimer(withTimeInterval: 10.0, repeats: true) { [weak self] _ in guard let strongSelf = self else { return } - Task { - await strongSelf.fetchAndUpdateSubscriptionDetails(cachePolicy: .reloadIgnoringLocalCacheData) - } + strongSelf.fetchAndUpdateSubscriptionDetails(cachePolicy: .reloadIgnoringLocalCacheData) } } + @MainActor private func updateSubscriptionsStatusMessage(status: Subscription.Status, date: Date, product: String, billingPeriod: Subscription.BillingPeriod) { let statusString = (status == .autoRenewable) ? UserText.subscriptionRenews : UserText.subscriptionExpires state.subscriptionDetails = UserText.subscriptionInfo(status: statusString, expiration: dateFormatter.string(from: date)) @@ -137,26 +140,26 @@ final class SubscriptionSettingsViewModel: ObservableObject { } func displayGoogleView(_ value: Bool) { - if value != state.shouldDisplayGoogleView { - state.shouldDisplayGoogleView = value + if value != state.isShowingGoogleView { + state.isShowingGoogleView = value } } func displayStripeView(_ value: Bool) { - if value != state.shouldDisplayStripeView { - state.shouldDisplayStripeView = value + if value != state.isShowingStripeView { + state.isShowingStripeView = value } } func displayRemovalNotice(_ value: Bool) { - if value != state.shouldDisplayRemovalNotice { - state.shouldDisplayRemovalNotice = value + if value != state.isShowingRemovalNotice { + state.isShowingRemovalNotice = value } } func displayFAQView(_ value: Bool) { - if value != state.shouldDisplayFAQView { - state.shouldDisplayFAQView = value + if value != state.isShowingFAQView { + state.isShowingFAQView = value } } diff --git a/DuckDuckGo/Subscription/Views/RootPresentationMode.swift b/DuckDuckGo/Subscription/Views/RootPresentationMode.swift deleted file mode 100644 index e43fc4bc08..0000000000 --- a/DuckDuckGo/Subscription/Views/RootPresentationMode.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// RootPresentationMode.swift -// DuckDuckGo -// -// 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 - -/* - iOS15 does not support NavigationStack navigation so this creates a 'RootPresentationMode' - environment that views can use to create a binding for dismissal of the whole stack of views - See: https://stackoverflow.com/questions/57334455/how-can-i-pop-to-the-root-view-using-swiftui - */ -struct RootPresentationModeKey: EnvironmentKey { - static let defaultValue: Binding = .constant(RootPresentationMode()) -} - -extension EnvironmentValues { - var rootPresentationMode: Binding { - get { return self[RootPresentationModeKey.self] } - set { self[RootPresentationModeKey.self] = newValue } - } -} - -typealias RootPresentationMode = Bool - -extension RootPresentationMode { - - public mutating func dismiss() { - self.toggle() - } -} diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift new file mode 100644 index 0000000000..037e36740f --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -0,0 +1,62 @@ +// +// SubscriptionContainerView.swift +// DuckDuckGo +// +// 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 SwiftUI + +#if SUBSCRIPTION +@available(iOS 15.0, *) +struct SubscriptionContainerView: View { + + enum CurrentView { + case subscribe, restore + } + + @Environment(\.dismiss) var dismiss + @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator + @State private var currentViewState: CurrentView + private let flowViewModel: SubscriptionFlowViewModel + private let restoreViewModel: SubscriptionRestoreViewModel + private let emailViewModel: SubscriptionEmailViewModel + + init(currentView: CurrentView) { + _currentViewState = State(initialValue: currentView) + + let userScript = SubscriptionPagesUserScript() + let subFeature = SubscriptionPagesUseSubscriptionFeature() + flowViewModel = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) + restoreViewModel = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) + emailViewModel = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) + } + + var body: some View { + VStack { + switch currentViewState { + case .subscribe: + SubscriptionFlowView(viewModel: flowViewModel, + currentView: $currentViewState).environmentObject(subscriptionNavigationCoordinator) + case .restore: + SubscriptionRestoreView(viewModel: restoreViewModel, + emailViewModel: emailViewModel, + currentView: $currentViewState).environmentObject(subscriptionNavigationCoordinator) + } + } + } +} +#endif diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 6371cad8e6..6fc62f7643 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -21,18 +21,21 @@ import SwiftUI import Foundation import Core +import Combine @available(iOS 15.0, *) struct SubscriptionEmailView: View { - @StateObject var viewModel = SubscriptionEmailViewModel() + @StateObject var viewModel: SubscriptionEmailViewModel + @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator @Environment(\.dismiss) var dismiss - - @State var shouldDisplayInactiveError = false - @State var shouldDisplayNavigationError = false - @State var isModal = true - - var onDismissStack: (() -> Void)? + + @State var isPresentingInactiveError = false + @State var isPresentingNavigationError = false + @State var backButtonText = UserText.backButtonTitle + @State private var isShowingITR = false + @State private var isShowingDBP = false + @State private var isShowingNetP = false enum Constants { static let navButtonPadding: CGFloat = 20.0 @@ -40,14 +43,23 @@ struct SubscriptionEmailView: View { } var body: some View { + // Hidden Navigation Links for Onboarding sections + NavigationLink(destination: NetworkProtectionRootView(inviteCompletion: {}).navigationViewStyle(.stack), + isActive: $isShowingNetP, + label: { EmptyView() }) + NavigationLink(destination: SubscriptionITPView().navigationViewStyle(.stack), + isActive: $isShowingITR, + label: { EmptyView() }) + NavigationLink(destination: SubscriptionPIRView().navigationViewStyle(.stack), + isActive: $isShowingDBP, + label: { EmptyView() }) + baseView + .toolbar { ToolbarItemGroup(placement: .navigationBarLeading) { browserBackButton } - ToolbarItem(placement: .navigationBarTrailing) { - closeButton - } } .navigationBarTitleDisplayMode(.inline) .navigationViewStyle(.stack) @@ -55,7 +67,7 @@ struct SubscriptionEmailView: View { .tint(Color.init(designSystemColor: .textPrimary)) .accentColor(Color.init(designSystemColor: .textPrimary)) - .alert(isPresented: $shouldDisplayInactiveError) { + .alert(isPresented: $isPresentingInactiveError) { Alert( title: Text(UserText.subscriptionRestoreEmailInactiveTitle), message: Text(UserText.subscriptionRestoreEmailInactiveMessage), @@ -65,7 +77,7 @@ struct SubscriptionEmailView: View { ) } - .alert(isPresented: $shouldDisplayNavigationError) { + .alert(isPresented: $isPresentingNavigationError) { Alert( title: Text(UserText.subscriptionBackendErrorTitle), message: Text(UserText.subscriptionBackendErrorMessage), @@ -73,53 +85,58 @@ struct SubscriptionEmailView: View { viewModel.dismissView() }) } - - .onAppear { - viewModel.onAppear() - } - .onChange(of: viewModel.state.shouldDisplayInactiveError) { value in - shouldDisplayInactiveError = value + .onChange(of: viewModel.state.isPresentingInactiveError) { value in + isPresentingInactiveError = value } .onChange(of: viewModel.state.shouldDisplaynavigationError) { value in - shouldDisplayNavigationError = value - } - - .onChange(of: viewModel.state.shouldDismissStack) { _ in - onDismissStack?() + isPresentingNavigationError = value } // Observe changes to shouldDismissView .onChange(of: viewModel.state.shouldDismissView) { shouldDismiss in if shouldDismiss { dismiss() - - // Reset shouldDismissView after dismissal to ensure it can trigger again - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - viewModel.resetDismissalState() - } + } + } + + .onChange(of: viewModel.state.shouldPopToSubscriptionSettings) { shouldDismiss in + if shouldDismiss { + subscriptionNavigationCoordinator.shouldPopToSubscriptionSettings = true + } + } + + .onChange(of: viewModel.state.shouldPopToAppSettings) { shouldDismiss in + if shouldDismiss { + subscriptionNavigationCoordinator.shouldPopToAppSettings = true + } + } + + .onChange(of: viewModel.state.selectedFeature) { feature in + switch feature { + case .dbp: + self.isShowingDBP = true + case .itr: + self.isShowingITR = true + case .netP: + self.isShowingNetP = true + default: + break } } .navigationTitle(viewModel.viewTitle) - .onAppear(perform: { + .onFirstAppear { setUpAppearances() - viewModel.onAppear() - }) + viewModel.onFirstAppear() + } } // MARK: - - @ViewBuilder - private var closeButton: some View { - if isModal { - Button(UserText.subscriptionCloseButton) { onDismissStack?() } - } - } - private var baseView: some View { ZStack { VStack { @@ -131,16 +148,14 @@ struct SubscriptionEmailView: View { @ViewBuilder private var browserBackButton: some View { - if viewModel.shouldDisplayBackButton() { - Button(action: { - Task { await viewModel.navigateBack() } - }, label: { - HStack(spacing: 0) { - Image(systemName: Constants.backButtonImage) - Text(UserText.backButtonTitle).foregroundColor(Color(designSystemColor: .textPrimary)) - } - }) - } + Button(action: { + Task { await viewModel.navigateBack() } + }, label: { + HStack(spacing: 0) { + Image(systemName: Constants.backButtonImage) + Text(viewModel.state.backButtonTitle).foregroundColor(Color(designSystemColor: .textPrimary)) + } + }) } private func setUpAppearances() { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift b/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift index 5edfcc0155..4b5af9a3d5 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionExternalLinkView.swift @@ -50,10 +50,10 @@ struct SubscriptionExternalLinkView: View { .navigationViewStyle(.stack) .navigationTitle(title ?? "") - .onAppear(perform: { + .onFirstAppear { setUpAppearances() - viewModel.initializeView() - }) + viewModel.onFirstAppear() + } }.tint(Color(designSystemColor: .textPrimary)) } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index bd11a7d3ea..3f8d3fe006 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -25,13 +25,20 @@ import Core @available(iOS 15.0, *) struct SubscriptionFlowView: View { - + @Environment(\.dismiss) var dismiss - @StateObject var viewModel = SubscriptionFlowViewModel() - + @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator + @StateObject var viewModel: SubscriptionFlowViewModel + + @State private var isPurchaseInProgress = false + @State private var isShowingITR = false + @State private var isShowingDBP = false + @State private var isShowingNetP = false + @Binding var currentView: SubscriptionContainerView.CurrentView + // Local View State @State private var errorMessage: SubscriptionErrorMessage = .general - @State private var shouldPresentError: Bool = false + @State private var isPresentingError: Bool = false enum Constants { static let daxLogo = "Home" @@ -49,38 +56,38 @@ struct SubscriptionFlowView: View { } var body: some View { - NavigationView { - baseView - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - backButton - } - ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() - } + + // Hidden Navigation Links for Onboarding sections + NavigationLink(destination: NetworkProtectionRootView(inviteCompletion: {}).navigationViewStyle(.stack), + isActive: $isShowingNetP, + label: { EmptyView() }) + NavigationLink(destination: SubscriptionITPView().navigationViewStyle(.stack), + isActive: $isShowingITR, + label: { EmptyView() }) + NavigationLink(destination: SubscriptionPIRView().navigationViewStyle(.stack), + isActive: $isShowingDBP, + label: { EmptyView() }) + + baseView + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + backButton + } + ToolbarItem(placement: .principal) { + HStack { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(viewModel.viewTitle).daxBodyRegular() } } - .edgesIgnoringSafeArea(.top) - .navigationBarTitleDisplayMode(.inline) - .navigationBarHidden(!viewModel.state.shouldShowNavigationBar).animation(.easeOut) - } - .applyInsetGroupedListStyle() - .tint(Color(designSystemColor: .textPrimary)) - } - - @ViewBuilder - private var dismissButton: some View { - Button(action: { - viewModel.finalizeSubscriptionFlow() - }, label: { Text(UserText.subscriptionCloseButton) }) - .padding(Constants.navButtonPadding) - .contentShape(Rectangle()) - .tint(Color(designSystemColor: .textPrimary)) + } + .navigationBarTitleDisplayMode(.inline) + .navigationBarBackButtonHidden(viewModel.state.canNavigateBack || viewModel.subFeature.transactionStatus != .idle) + .interactiveDismissDisabled(viewModel.subFeature.transactionStatus != .idle) + .edgesIgnoringSafeArea(.bottom) + .tint(Color(designSystemColor: .textPrimary)) } @ViewBuilder @@ -116,27 +123,32 @@ struct SubscriptionFlowView: View { private var baseView: some View { ZStack(alignment: .top) { webView - - // Show a dismiss button while the bar is not visible - // But it should be hidden while performing a transaction - if !viewModel.state.shouldShowNavigationBar && viewModel.state.transactionStatus == .idle { - HStack { - backButton.padding(.leading, Constants.navButtonPadding) - Spacer() - dismissButton - } + } + + .onChange(of: viewModel.state.selectedFeature) { feature in + switch feature { + case .dbp: + self.isShowingDBP = true + case .itr: + self.isShowingITR = true + case .netP: + self.isShowingNetP = true + default: + break } } - .onChange(of: viewModel.state.shouldDismissView) { result in + .onChange(of: viewModel.state.shouldActivateSubscription) { result in if result { - dismiss() + withAnimation { + currentView = .restore + } } } .onChange(of: viewModel.state.transactionError) { value in - if !shouldPresentError { + if !isPresentingError { let displayError: Bool = { switch value { case .hasActiveSubscription: @@ -154,30 +166,29 @@ struct SubscriptionFlowView: View { }() if displayError { - shouldPresentError = true + isPresentingError = true } } } - .onAppear(perform: { + .onFirstAppear { setUpAppearances() - Task { await viewModel.onAppear() } - - }) + Task { await viewModel.onFirstAppear() } + } + + .onFirstDisappear { + Task { await viewModel.onFirstDisappear() } + } - .onDisappear(perform: { - viewModel.onDisappear() - }) + .onAppear { + Task { await viewModel.onFirstAppear() } + } - .alert(isPresented: $shouldPresentError) { + .alert(isPresented: $isPresentingError) { getAlert(error: self.errorMessage) } - // The trailing close button should be hidden when a transaction is in progress - .navigationBarItems(trailing: viewModel.state.transactionStatus == .idle - ? Button(UserText.subscriptionCloseButton) { viewModel.finalizeSubscriptionFlow() } - : nil) } private func getAlert(error: SubscriptionErrorMessage) -> Alert { @@ -189,7 +200,7 @@ struct SubscriptionFlowView: View { message: Text(UserText.subscriptionFoundText), primaryButton: .cancel(Text(UserText.subscriptionFoundCancel)) { viewModel.clearTransactionError() - viewModel.finalizeSubscriptionFlow() + dismiss() }, secondaryButton: .default(Text(UserText.subscriptionFoundRestore)) { viewModel.restoreAppstoreTransaction() @@ -201,7 +212,7 @@ struct SubscriptionFlowView: View { message: Text(UserText.subscriptionAppStoreErrorMessage), dismissButton: .cancel(Text(UserText.actionOK)) { viewModel.clearTransactionError() - viewModel.finalizeSubscriptionFlow() + dismiss() } ) case .backend, .general: @@ -209,7 +220,8 @@ struct SubscriptionFlowView: View { title: Text(UserText.subscriptionBackendErrorTitle), message: Text(UserText.subscriptionBackendErrorMessage), dismissButton: .cancel(Text(UserText.subscriptionBackendErrorButton)) { - viewModel.finalizeSubscriptionFlow() + viewModel.clearTransactionError() + dismiss() } ) } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index e411c109fd..5904854dc2 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -51,40 +51,35 @@ struct SubscriptionITPView: View { } var body: some View { - NavigationView { - baseView - .toolbar { - ToolbarItemGroup(placement: .navigationBarLeading) { - backButton - } - ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() - } - } - ToolbarItem(placement: .navigationBarTrailing) { - shareButton - } - ToolbarItem(placement: .navigationBarTrailing) { - Button(UserText.subscriptionCloseButton) { dismiss() } + + baseView + + .toolbar { + ToolbarItemGroup(placement: .navigationBarLeading) { + backButton + } + ToolbarItem(placement: .principal) { + HStack { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(viewModel.viewTitle).daxBodyRegular() } } - .edgesIgnoringSafeArea(.all) - .navigationBarTitleDisplayMode(.inline) - .navigationBarHidden(!viewModel.shouldShowNavigationBar && !viewModel.isDownloadableContent).animation(.snappy) - - .onAppear(perform: { - setUpAppearances() - viewModel.initializeView() - }) - + ToolbarItem(placement: .navigationBarTrailing) { + shareButton + } } + .edgesIgnoringSafeArea(.bottom) + .navigationBarBackButtonHidden(viewModel.canNavigateBack) + .navigationBarTitleDisplayMode(.inline) .tint(Color(designSystemColor: .textPrimary)) + .onFirstAppear { + viewModel.onFirstAppear() + setUpAppearances() + } .alert(isPresented: $viewModel.navigationError) { Alert( @@ -109,17 +104,6 @@ struct SubscriptionITPView: View { private var baseView: some View { ZStack(alignment: .top) { webView - - // Show a dismiss button while the bar is not visible - // But it should be hidden while performing a transaction - if !shouldShowNavigationBar { - HStack { - backButton.padding(.leading, Constants.navButtonPadding) - Spacer() - dismissButton - } - } - } } @@ -157,15 +141,6 @@ struct SubscriptionITPView: View { } } - @ViewBuilder - private var dismissButton: some View { - Button(action: { dismiss() }, label: { Text(UserText.subscriptionCloseButton) }) - .padding(Constants.navButtonPadding) - .contentShape(Rectangle()) - .tint(Color(designSystemColor: .textPrimary)) - } - - private func setUpAppearances() { let navAppearance = UINavigationBar.appearance() navAppearance.backgroundColor = UIColor(designSystemColor: .surface) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionNavigationCoordinator.swift b/DuckDuckGo/Subscription/Views/SubscriptionNavigationCoordinator.swift new file mode 100644 index 0000000000..b8fe1f7867 --- /dev/null +++ b/DuckDuckGo/Subscription/Views/SubscriptionNavigationCoordinator.swift @@ -0,0 +1,25 @@ +// +// SubscriptionNavigationCoordinator.swift +// DuckDuckGo +// +// 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 + +final class SubscriptionNavigationCoordinator: ObservableObject { + @Published var shouldPopToSubscriptionSettings: Bool = false + @Published var shouldPopToAppSettings: Bool = false +} diff --git a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift index 6911051167..9756c040ca 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift @@ -50,42 +50,32 @@ struct SubscriptionPIRView: View { } var body: some View { - NavigationView { - ZStack { - gradientBackground - ScrollView { - VStack { - header - .padding(.top, Constants.headerPadding) - baseView - .frame(maxWidth: 600) - } + ZStack { + gradientBackground + ScrollView { + VStack { + baseView + .frame(maxWidth: 600) } - } - .edgesIgnoringSafeArea(.all) - }.onAppear(perform: { - viewModel.onAppear() - }) - } - - private var header: some View { - GeometryReader { geometry in - HStack { - Spacer().frame(width: geometry.size.width / 3) - HStack(alignment: .center) { + + } + .toolbar { + ToolbarItem(placement: .principal) { + HStack { Image(Constants.daxLogo) .resizable() .aspectRatio(contentMode: .fit) .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) Text(viewModel.viewTitle).daxBodyRegular() } - .frame(width: geometry.size.width / 3, alignment: .center) - dismissButton - .frame(width: geometry.size.width / 3, alignment: .trailing) } } + .onFirstAppear { + viewModel.onFirstAppear() + } } + private var gradientBackground: some View { ZStack { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index a85d37b1ff..f010b8aef0 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -28,20 +28,17 @@ import Core // swiftlint:disable type_body_length struct SubscriptionRestoreView: View { - enum Source { - case addAnotherDevice - case settings - } - @Environment(\.dismiss) var dismiss - @StateObject var viewModel: SubscriptionRestoreViewModel = SubscriptionRestoreViewModel() - + + @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator + @StateObject var viewModel: SubscriptionRestoreViewModel + @StateObject var emailViewModel: SubscriptionEmailViewModel + @State private var isAlertVisible = false - @State private var shouldShowWelcomePage = false - @State private var shouldNavigateToActivationFlow = false - @State var isModal = true - var source: SubscriptionRestoreView.Source = .settings - + @State private var isShowingWelcomePage = false + @State private var isShowingActivationFlow = false + @Binding var currentView: SubscriptionContainerView.CurrentView + private enum Constants { static let heroImage = "ManageSubscriptionHero" static let appleIDIcon = "Platform-Apple-16-subscriptions" @@ -76,10 +73,8 @@ struct SubscriptionRestoreView: View { } } else { ZStack { + baseView - NavigationView { - baseView - } if viewModel.state.transactionStatus != .idle { PurchaseInProgressView(status: getTransactionStatus()) } @@ -87,10 +82,8 @@ struct SubscriptionRestoreView: View { } } } - - @ViewBuilder - private var baseView: some View { - + + private var contentView: some View { Group { ScrollView { VStack(spacing: Constants.sectionSpacing) { @@ -100,10 +93,8 @@ struct SubscriptionRestoreView: View { Spacer() // Hidden link to display Email Activation View - NavigationLink(destination: SubscriptionEmailView(viewModel: viewModel.emailViewModel, - isModal: isModal, - onDismissStack: { viewModel.dismissView() }), - isActive: $shouldNavigateToActivationFlow) { + NavigationLink(destination: SubscriptionEmailView(viewModel: emailViewModel).environmentObject(subscriptionNavigationCoordinator), + isActive: $isShowingActivationFlow) { EmptyView() }.isDetailLink(false) @@ -118,57 +109,57 @@ struct SubscriptionRestoreView: View { .navigationBarBackButtonHidden(viewModel.state.transactionStatus != .idle) .navigationBarTitleDisplayMode(.inline) .applyInsetGroupedListStyle() - .navigationBarItems(trailing: closeButton) + .interactiveDismissDisabled(viewModel.subFeature.transactionStatus != .idle) .tint(Color.init(designSystemColor: .textPrimary)) .accentColor(Color.init(designSystemColor: .textPrimary)) } - - .alert(isPresented: $isAlertVisible) { getAlert() } - - .onChange(of: viewModel.state.activationResult) { result in - if result != .unknown { - isAlertVisible = true + } + + @ViewBuilder + private var baseView: some View { + + contentView + .alert(isPresented: $isAlertVisible) { getAlert() } + + .onChange(of: viewModel.state.activationResult) { result in + if result != .unknown { + isAlertVisible = true + } } - } - - // Navigation Flow Binding - .onChange(of: viewModel.state.shouldNavigateToActivationFlow) { result in - shouldNavigateToActivationFlow = result - } - .onChange(of: shouldNavigateToActivationFlow) { result in - viewModel.showActivationFlow(result) - } - .onChange(of: viewModel.state.shouldDismissView) { result in - if result { - dismiss() + + // Navigation Flow Binding + .onChange(of: viewModel.state.isShowingActivationFlow) { result in + isShowingActivationFlow = result } - } - .onChange(of: viewModel.state.shouldShowPlans) { result in - if result { - dismiss() + .onChange(of: isShowingActivationFlow) { result in + viewModel.showActivationFlow(result) } - } - .onAppear { - viewModel.onAppear() - setUpAppearances() - - switch source { - case .addAnotherDevice: - Pixel.fire(pixel: .privacyProSettingsAddDevice, debounce: 2) - default: break + + .onChange(of: viewModel.state.shouldDismissView) { result in + if result { + dismiss() + } } - } + + .onChange(of: viewModel.state.shouldShowPlans) { result in + if result { + currentView = .subscribe + } + } + + .onFirstAppear { + Task { await viewModel.onFirstAppear() } + setUpAppearances() + } + + .onFirstDisappear { + Task { await viewModel.onFirstDisappear() } + } + } // MARK: - - @ViewBuilder - private var closeButton: some View { - if isModal { - Button(UserText.subscriptionCloseButton) { viewModel.dismissView() } - } - } - private var emailView: some View { emailCellContent .padding(Constants.boxPadding) @@ -190,37 +181,39 @@ struct SubscriptionRestoreView: View { .foregroundColor(Color(designSystemColor: .textPrimary)) } - VStack(alignment: .leading) { - if !viewModel.state.isAddingDevice { - Text(UserText.subscriptionActivateEmailDescription) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - getCellButton(buttonText: UserText.subscriptionActivateEmailButton, - action: { - DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailStart) - DailyPixel.fire(pixel: .privacyProWelcomeAddDevice) - viewModel.showActivationFlow(true) - }) - } else if viewModel.state.subscriptionEmail == nil { - Text(UserText.subscriptionAddDeviceEmailDescription) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - getCellButton(buttonText: UserText.subscriptionRestoreAddEmailButton, - action: { - Pixel.fire(pixel: .privacyProAddDeviceEnterEmail, debounce: 1) - viewModel.showActivationFlow(true) - }) - } else { - Text(viewModel.state.subscriptionEmail ?? "").daxSubheadSemibold() - Text(UserText.subscriptionManageEmailDescription) - .daxSubheadRegular() - .foregroundColor(Color(designSystemColor: .textSecondary)) - HStack { - getCellButton(buttonText: UserText.subscriptionManageEmailButton, + if !viewModel.state.isLoading { + VStack(alignment: .leading) { + if !viewModel.state.isAddingDevice { + Text(UserText.subscriptionActivateEmailDescription) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + getCellButton(buttonText: UserText.subscriptionActivateEmailButton, + action: { + DailyPixel.fireDailyAndCount(pixel: .privacyProRestorePurchaseEmailStart) + DailyPixel.fire(pixel: .privacyProWelcomeAddDevice) + viewModel.showActivationFlow(true) + }) + } else if viewModel.state.subscriptionEmail == nil { + Text(UserText.subscriptionAddDeviceEmailDescription) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + getCellButton(buttonText: UserText.subscriptionRestoreAddEmailButton, action: { - Pixel.fire(pixel: .privacyProSubscriptionManagementEmail, debounce: 1) + Pixel.fire(pixel: .privacyProAddDeviceEnterEmail, debounce: 1) viewModel.showActivationFlow(true) }) + } else { + Text(viewModel.state.subscriptionEmail ?? "").daxSubheadSemibold() + Text(UserText.subscriptionManageEmailDescription) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) + HStack { + getCellButton(buttonText: UserText.subscriptionManageEmailButton, + action: { + Pixel.fire(pixel: .privacyProSubscriptionManagementEmail, debounce: 1) + viewModel.showActivationFlow(true) + }) + } } } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index f175232a3c..e93ca2e55f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -22,23 +22,19 @@ import SwiftUI import DesignResourcesKit import Core -class SceneEnvironment: ObservableObject { - weak var windowScene: UIWindowScene? -} - #if SUBSCRIPTION @available(iOS 15.0, *) struct SubscriptionSettingsView: View { - - @Environment(\.presentationMode) var presentationMode + @Environment(\.dismiss) var dismiss @StateObject var viewModel = SubscriptionSettingsViewModel() - @StateObject var sceneEnvironment = SceneEnvironment() + @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator - @State var shouldDisplayStripeView = false - @State var shouldDisplayGoogleView = false - @State var shouldDisplayRemovalNotice = false - @State var shouldDisplayFAQView = false + @State var isShowingStripeView = false + @State var isShowingGoogleView = false + @State var isShowingRemovalNotice = false + @State var isShowingFAQView = false + @State var isShowingRestoreView = false var body: some View { optionsView @@ -78,7 +74,7 @@ struct SubscriptionSettingsView: View { Task { viewModel.manageSubscription() } }, isButton: true) - .sheet(isPresented: $shouldDisplayStripeView) { + .sheet(isPresented: $isShowingStripeView) { if let stripeViewModel = viewModel.state.stripeViewModel { SubscriptionExternalLinkView(viewModel: stripeViewModel, title: UserText.subscriptionManagePlan) } @@ -89,12 +85,14 @@ struct SubscriptionSettingsView: View { private var devicesSection: some View { Section(header: Text(UserText.subscriptionManageDevices)) { - NavigationLink(destination: SubscriptionRestoreView(isModal: false, source: .addAnotherDevice)) { + NavigationLink(destination: SubscriptionContainerView(currentView: .restore) + .environmentObject(subscriptionNavigationCoordinator), + isActive: $isShowingRestoreView) { SettingsCustomCell(content: { Text(UserText.subscriptionAddDeviceButton) .daxBodyRegular() }) - } + }.isDetailLink(false) SettingsCustomCell(content: { Text(UserText.subscriptionRemoveFromDevice) @@ -126,7 +124,7 @@ struct SubscriptionSettingsView: View { @ViewBuilder private var optionsView: some View { NavigationLink(destination: SubscriptionGoogleView(), - isActive: $shouldDisplayGoogleView) { + isActive: $isShowingGoogleView) { EmptyView() } @@ -147,40 +145,46 @@ struct SubscriptionSettingsView: View { } // Google Binding - .onChange(of: viewModel.state.shouldDisplayGoogleView) { value in - shouldDisplayGoogleView = value + .onChange(of: viewModel.state.isShowingGoogleView) { value in + isShowingGoogleView = value } - .onChange(of: shouldDisplayGoogleView) { value in + .onChange(of: isShowingGoogleView) { value in viewModel.displayGoogleView(value) } // Stripe Binding - .onChange(of: viewModel.state.shouldDisplayStripeView) { value in - shouldDisplayStripeView = value + .onChange(of: viewModel.state.isShowingStripeView) { value in + isShowingStripeView = value } - .onChange(of: shouldDisplayStripeView) { value in + .onChange(of: isShowingStripeView) { value in viewModel.displayStripeView(value) } // Removal Notice - .onChange(of: viewModel.state.shouldDisplayRemovalNotice) { value in - shouldDisplayRemovalNotice = value + .onChange(of: viewModel.state.isShowingRemovalNotice) { value in + isShowingRemovalNotice = value } - .onChange(of: shouldDisplayRemovalNotice) { value in + .onChange(of: isShowingRemovalNotice) { value in viewModel.displayRemovalNotice(value) } // Removal Notice - .onChange(of: viewModel.state.shouldDisplayFAQView) { value in - shouldDisplayFAQView = value + .onChange(of: viewModel.state.isShowingFAQView) { value in + isShowingFAQView = value } - .onChange(of: shouldDisplayFAQView) { value in + .onChange(of: isShowingFAQView) { value in viewModel.displayFAQView(value) } + + .onReceive(subscriptionNavigationCoordinator.$shouldPopToSubscriptionSettings) { shouldDismiss in + if shouldDismiss { + isShowingRestoreView = false + } + } // Remove subscription - .alert(isPresented: $shouldDisplayRemovalNotice) { + .alert(isPresented: $isShowingRemovalNotice) { Alert( title: Text(UserText.subscriptionRemoveFromDeviceConfirmTitle), message: Text(UserText.subscriptionRemoveFromDeviceConfirmText), @@ -189,17 +193,17 @@ struct SubscriptionSettingsView: View { secondaryButton: .destructive(Text(UserText.subscriptionRemove)) { Pixel.fire(pixel: .privacyProSubscriptionManagementRemoval) viewModel.removeSubscription() - presentationMode.wrappedValue.dismiss() + dismiss() } ) } - .sheet(isPresented: $shouldDisplayFAQView, content: { + .sheet(isPresented: $isShowingFAQView, content: { SubscriptionExternalLinkView(viewModel: viewModel.state.FAQViewModel, title: UserText.subscriptionFAQ) }) - .onAppear { - viewModel.fetchAndUpdateSubscriptionDetails() + .onFirstAppear { + viewModel.onFirstAppear() } } @@ -214,18 +218,6 @@ struct SubscriptionSettingsView: View { } #endif - -#if SUBSCRIPTION && DEBUG -@available(iOS 15.0, *) - -struct SubscriptionSettingsView_Previews: PreviewProvider { - static var previews: some View { - NavigationView { - SubscriptionSettingsView().navigationBarTitleDisplayMode(.inline) - } - } -} - // Commented out because CI fails if a SwiftUI preview is enabled https://app.asana.com/0/414709148257752/1206774081310425/f // @available(iOS 15.0, *) // struct SubscriptionSettingsView_Previews: PreviewProvider { @@ -233,5 +225,3 @@ struct SubscriptionSettingsView_Previews: PreviewProvider { // SubscriptionSettingsView() // } // } - -#endif From 8df8b81366d2b657f2fdb3f4784afb9d26a7961f Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Wed, 3 Apr 2024 00:54:37 +0200 Subject: [PATCH 190/245] Release 7.114.0-0 (#2663) --- Configuration/Version.xcconfig | 2 +- .../AppPrivacyConfigurationDataProvider.swift | 4 +- Core/ios-config.json | 70 ++++++++++++++++--- DuckDuckGo.xcodeproj/project.pbxproj | 56 +++++++-------- DuckDuckGo/Settings.bundle/Root.plist | 2 +- 5 files changed, 93 insertions(+), 41 deletions(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 1505c5ea25..cb3615d8f4 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.113.0 +MARKETING_VERSION = 7.114.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index 2e95e5c71c..69d814d8ee 100644 --- a/Core/AppPrivacyConfigurationDataProvider.swift +++ b/Core/AppPrivacyConfigurationDataProvider.swift @@ -23,8 +23,8 @@ import BrowserServicesKit final public class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"4cac4f65624262686a265d3b95a8374b\"" - public static let embeddedDataSHA = "6da9ab104a4b2adca51862ad942821e629a24929a95016b045f7bdd0028e1f71" + public static let embeddedDataETag = "\"24f5176757f3bafd1ea7e457413fcff0\"" + public static let embeddedDataSHA = "f318ef32f8069a458e8a4505eb8f53e3c25b60311d2daf763029c1e2606cea88" } public var embeddedDataEtag: String { diff --git a/Core/ios-config.json b/Core/ios-config.json index dd4ef388f4..132b258876 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1711567148287, + "version": 1712088935529, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -270,6 +270,21 @@ { "domain": "www.ffbb.com" }, + { + "domain": "hello-hossy.com" + }, + { + "domain": "bitsofwar.com" + }, + { + "domain": "mitglieder.franzspitzer.de" + }, + { + "domain": "disneyplus.com" + }, + { + "domain": "condell-ltd.com" + }, { "domain": "earth.google.com" }, @@ -313,7 +328,7 @@ } } }, - "hash": "3149ef7db2b6835e6d0dc7c2a843cfff" + "hash": "0822624a58797e40e1816f4ce3c8d2a7" }, "autofill": { "exceptions": [ @@ -1190,7 +1205,9 @@ "videoElement": "#player video", "videoElementContainer": "#player .html5-video-player", "hoverExcluded": [], - "clickExcluded": [], + "clickExcluded": [ + "ytd-thumbnail-overlay-toggle-button-renderer" + ], "allowedEventTargets": [ ".ytp-inline-preview-scrim", ".ytd-video-preview", @@ -1237,7 +1254,7 @@ ] }, "state": "disabled", - "hash": "04c97c3c7425cc18984dd34eda95a112" + "hash": "59ef4ce748dc657aa3d42756131dcb16" }, "elementHiding": { "exceptions": [ @@ -1416,6 +1433,10 @@ "selector": ".top-ad", "type": "hide-empty" }, + { + "selector": "#topAd", + "type": "hide-empty" + }, { "selector": ".ad-banner-container", "type": "hide-empty" @@ -1496,6 +1517,10 @@ "selector": ".adcontainer", "type": "closest-empty" }, + { + "selector": ".adContainer", + "type": "hide-empty" + }, { "selector": ".ad-current", "type": "hide-empty" @@ -1981,6 +2006,14 @@ } ] }, + { + "domain": "blackhatworld.com", + "rules": [ + { + "type": "disable-default" + } + ] + }, { "domain": "bleacherreport.com", "rules": [ @@ -2087,6 +2120,10 @@ { "selector": ".skybox-top-wrapper", "type": "hide-empty" + }, + { + "selector": ".fixed-skybox-placeholder", + "type": "hide-empty" } ] }, @@ -2557,6 +2594,19 @@ } ] }, + { + "domain": "gap.com", + "rules": [ + { + "selector": ".ONhome1-recs", + "type": "hide" + }, + { + "selector": ".ONhome1-recs", + "type": "closest-empty" + } + ] + }, { "domain": "gbnews.com", "rules": [ @@ -4192,7 +4242,7 @@ ] }, "state": "enabled", - "hash": "313fb06b3f83c0fbe6ac7e7c358ee38e" + "hash": "5094a939eec2d7e518ff15d628eec23f" }, "exceptionHandler": { "exceptions": [ @@ -5248,6 +5298,7 @@ "fattoincasadabenedetta.it", "inquirer.com", "thesurfersview.com", + "twitchy.com", "wildrivers.lostcoastoutpost.com" ] }, @@ -6104,8 +6155,7 @@ { "rule": "v.fwmrm.net/ad", "domains": [ - "6play.fr", - "channel4.com" + "" ] } ] @@ -6269,6 +6319,7 @@ "domains": [ "arkadium.com", "bloomberg.com", + "cbssports.com", "gamak.tv", "games.washingtonpost.com", "metro.co.uk", @@ -6348,7 +6399,8 @@ "cosmicbook.news", "eatroyo.com", "thesimsresource.com", - "tradersync.com" + "tradersync.com", + "vanguardplan.com" ] } ] @@ -8037,7 +8089,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "9d0207772c29e4b74f7dd0356c9f84d9" + "hash": "7aae19055144405351a27ca946fa674d" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8bf3c09b35..5387560ef0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8270,7 +8270,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8307,7 +8307,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8399,7 +8399,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8427,7 +8427,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8577,7 +8577,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8603,7 +8603,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8668,7 +8668,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8703,7 +8703,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8737,7 +8737,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8768,7 +8768,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9055,7 +9055,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9086,7 +9086,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9115,7 +9115,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9149,7 +9149,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9180,7 +9180,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9213,11 +9213,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9451,7 +9451,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9478,7 +9478,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9511,7 +9511,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9549,7 +9549,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9585,7 +9585,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9620,11 +9620,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9798,11 +9798,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9831,10 +9831,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 0; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 0; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 0a06622ca2..6d8d1c4707 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.113.0 + 7.114.0 Key version Title From 71653ed943478cb746e42410478b81820f76c303 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 2 Apr 2024 18:57:09 -0700 Subject: [PATCH 191/245] Fix VPN internal user bugs (#2664) Task/Issue URL: https://app.asana.com/0/414235014887631/1206986434624665/f Tech Design URL: CC: Description: This PR fixes an issue where the VPN Thank You modal was interrupting the setup flow. --- DuckDuckGo/AppDelegate.swift | 9 ++++++++- DuckDuckGo/MainViewController.swift | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 79240a52de..42def30fc4 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -512,6 +512,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func stopTunnelAndShowThankYouMessagingIfNeeded() { + if AccountManager().isUserAuthenticated { + tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true + return + } + if vpnFeatureVisibility.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { presentVPNEarlyAccessOverAlert() @@ -519,7 +524,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let controller = NetworkProtectionTunnelController() if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": "thank-you-dialog" + ]) } await controller.stop() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 4f555e1323..92c0277371 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1420,6 +1420,7 @@ class MainViewController: UIViewController { @objc private func onNetworkProtectionAccountSignIn(_ notification: Notification) { tunnelDefaults.resetEntitlementMessaging() + tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true os_log("[NetP Subscription] Reset expired entitlement messaging", log: .networkProtection, type: .info) } @@ -1435,7 +1436,9 @@ class MainViewController: UIViewController { } if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": "entitlement-change" + ]) } await controller.stop() @@ -1449,7 +1452,9 @@ class MainViewController: UIViewController { let controller = NetworkProtectionTunnelController() if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": "account-signed-out" + ]) } await controller.stop() From 5b67fa4fbb6122b8e87dfe5eb03d14495ce50900 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 2 Apr 2024 19:36:01 -0700 Subject: [PATCH 192/245] [Release PR] Fix VPN internal user testing issues (#2665) Task/Issue URL: https://app.asana.com/0/414235014887631/1206986434624665/f Tech Design URL: CC: Description: This PR cherry picks #2664 to the release branch. --- DuckDuckGo/AppDelegate.swift | 9 ++++++++- DuckDuckGo/MainViewController.swift | 9 +++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 79240a52de..42def30fc4 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -512,6 +512,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } private func stopTunnelAndShowThankYouMessagingIfNeeded() { + if AccountManager().isUserAuthenticated { + tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true + return + } + if vpnFeatureVisibility.shouldShowThankYouMessaging() && !tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown { presentVPNEarlyAccessOverAlert() @@ -519,7 +524,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { let controller = NetworkProtectionTunnelController() if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": "thank-you-dialog" + ]) } await controller.stop() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 4f555e1323..92c0277371 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1420,6 +1420,7 @@ class MainViewController: UIViewController { @objc private func onNetworkProtectionAccountSignIn(_ notification: Notification) { tunnelDefaults.resetEntitlementMessaging() + tunnelDefaults.vpnEarlyAccessOverAlertAlreadyShown = true os_log("[NetP Subscription] Reset expired entitlement messaging", log: .networkProtection, type: .info) } @@ -1435,7 +1436,9 @@ class MainViewController: UIViewController { } if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": "entitlement-change" + ]) } await controller.stop() @@ -1449,7 +1452,9 @@ class MainViewController: UIViewController { let controller = NetworkProtectionTunnelController() if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled) + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": "account-signed-out" + ]) } await controller.stop() From 22ebea942b66437ccac50bdded8ce0d39e23c687 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Wed, 3 Apr 2024 04:26:44 +0100 Subject: [PATCH 193/245] fix flash of dax dialog onboarding text (#2662) Task/Issue URL: https://app.asana.com/0/414709148257752/1206983882892075/f Tech Design URL: CC: Description: Fix a flash of text when the onboarding loads caused by the MainVC refactoring. --- DuckDuckGo/DaxOnboardingViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DaxOnboardingViewController.swift b/DuckDuckGo/DaxOnboardingViewController.swift index 05939278ad..1e92a93f79 100644 --- a/DuckDuckGo/DaxOnboardingViewController.swift +++ b/DuckDuckGo/DaxOnboardingViewController.swift @@ -69,7 +69,8 @@ class DaxOnboardingViewController: UIViewController, Onboarding { guard !view.isHidden else { return } daxDialogContainerHeight.constant = daxDialog?.calculateHeight() ?? 0 - + self.daxDialog?.reset() + DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDelay) { self.transitionFromOnboarding() } From fa0be08e277510725c2e7a10edfbd18a7cfe0286 Mon Sep 17 00:00:00 2001 From: Chris Brind Date: Wed, 3 Apr 2024 10:52:20 +0100 Subject: [PATCH 194/245] cherry pick #2662 - fix dax dialog flash --- DuckDuckGo/DaxOnboardingViewController.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DaxOnboardingViewController.swift b/DuckDuckGo/DaxOnboardingViewController.swift index 05939278ad..1e92a93f79 100644 --- a/DuckDuckGo/DaxOnboardingViewController.swift +++ b/DuckDuckGo/DaxOnboardingViewController.swift @@ -69,7 +69,8 @@ class DaxOnboardingViewController: UIViewController, Onboarding { guard !view.isHidden else { return } daxDialogContainerHeight.constant = daxDialog?.calculateHeight() ?? 0 - + self.daxDialog?.reset() + DispatchQueue.main.asyncAfter(deadline: .now() + Constants.animationDelay) { self.transitionFromOnboarding() } From 760b58a675ea20b59d2453696726375c6378cdb6 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Wed, 3 Apr 2024 23:24:47 +0200 Subject: [PATCH 195/245] Don't crash on assert in alpha builds (#2667) Task/Issue URL: https://app.asana.com/0/414235014887631/1206985884087562/f Description: As we saw in Crashes when opening from external links with auto-clear+expiry alpha builds can crash on asserts --- Configuration/Configuration-Alpha.xcconfig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Configuration/Configuration-Alpha.xcconfig b/Configuration/Configuration-Alpha.xcconfig index 940e9dfdcf..73ad56cfdc 100644 --- a/Configuration/Configuration-Alpha.xcconfig +++ b/Configuration/Configuration-Alpha.xcconfig @@ -29,3 +29,6 @@ GROUP_ID_PREFIX = group.com.duckduckgo.alpha // The keychain access group for subscriptions SUBSCRIPTION_APP_GROUP = com.duckduckgo.subscriptions.alpha + +// Prevents asserts from crashing alpha TF builds +OTHER_SWIFT_FLAGS[config=Alpha][arch=*][sdk=*] = $(inherited) -assert-config Release From ad2ec85dc45c67d9ca10eadd54060d1f2a2a6fa0 Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Thu, 4 Apr 2024 10:58:06 +0200 Subject: [PATCH 196/245] Make sure to clean HSTS and Alt Services dbs (#2647) Task/Issue URL: https://app.asana.com/0/0/1206930304246724/f Tech Design URL: CC: Description: Clean up HSTS and Alt Services. --- Core/WebCacheManager.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Core/WebCacheManager.swift b/Core/WebCacheManager.swift index 7974cca03f..faefc24b3b 100644 --- a/Core/WebCacheManager.swift +++ b/Core/WebCacheManager.swift @@ -141,7 +141,17 @@ extension WebCacheManager { } let dataStore = WKWebsiteDataStore.default() let cookies = await dataStore.httpCookieStore.allCookies() - await dataStore.removeData(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes(), modifiedSince: .distantPast) + var types = WKWebsiteDataStore.allWebsiteDataTypes() + types.insert("_WKWebsiteDataTypeMediaKeys") + types.insert("_WKWebsiteDataTypeHSTSCache") + types.insert("_WKWebsiteDataTypeSearchFieldRecentSearches") + types.insert("_WKWebsiteDataTypeResourceLoadStatistics") + types.insert("_WKWebsiteDataTypeCredentials") + types.insert("_WKWebsiteDataTypeAdClickAttributions") + types.insert("_WKWebsiteDataTypePrivateClickMeasurements") + types.insert("_WKWebsiteDataTypeAlternativeServices") + + await dataStore.removeData(ofTypes: types, modifiedSince: .distantPast) self.removeObservationsData() timeoutTask.cancel() return cookies From 8ce1531939b7b8b878d563159ff97209394d1848 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Thu, 4 Apr 2024 11:51:22 +0100 Subject: [PATCH 197/245] update swipe tabs feedback (#2618) --- Core/FeatureFlag.swift | 3 +-- DuckDuckGo/AppDelegate.swift | 4 +++- DuckDuckGo/MainViewController.swift | 24 ++++++++++++------------ DuckDuckGo/SwipeTabsCoordinator.swift | 15 +++++---------- DuckDuckGo/TabPreviewsSource.swift | 6 +----- 5 files changed, 22 insertions(+), 30 deletions(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index f14ae54cbf..579eda936d 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -34,7 +34,6 @@ public enum FeatureFlag: String { case networkProtection case networkProtectionWaitlistAccess case networkProtectionWaitlistActive - case swipeTabs case autoconsentOnByDefault case history } @@ -42,7 +41,7 @@ public enum FeatureFlag: String { extension FeatureFlag: FeatureFlagSourceProviding { public var source: FeatureFlagSource { switch self { - case .debugMenu, .appTrackingProtection, .swipeTabs: + case .debugMenu, .appTrackingProtection: return .internalOnly case .sync: return .remoteReleasable(.subfeature(SyncSubfeature.level0ShowSync)) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 42def30fc4..fa5cdf9eea 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -642,7 +642,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private func showKeyboardOnLaunch() { guard KeyboardSettings().onAppLaunch && showKeyboardIfSettingOn && shouldShowKeyboardOnLaunch() else { return } - self.mainViewController?.enterSearch() + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.mainViewController?.enterSearch() + } showKeyboardIfSettingOn = false } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 92c0277371..3157597a2a 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -304,8 +304,8 @@ class MainViewController: UIViewController { startOnboardingFlowIfNotSeenBefore() tabsBarController?.refresh(tabsModel: tabManager.model) - swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) - + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) + _ = AppWidthObserver.shared.willResize(toWidth: view.frame.width) applyWidth() @@ -340,7 +340,7 @@ class MainViewController: UIViewController { } } - func updatePreviewForCurrentTab() { + func updatePreviewForCurrentTab(completion: (() -> Void)? = nil) { assert(Thread.isMainThread) if !viewCoordinator.logoContainer.isHidden, @@ -349,6 +349,7 @@ class MainViewController: UIViewController { // Home screen with logo if let image = viewCoordinator.logoContainer.createImageSnapshot(inBounds: viewCoordinator.contentContainer.frame) { previewsSource.update(preview: image, forTab: tab) + completion?() } } else if let currentTab = self.tabManager.current(), currentTab.link != nil { @@ -357,12 +358,16 @@ class MainViewController: UIViewController { guard let image else { return } self.previewsSource.update(preview: image, forTab: currentTab.tabModel) + completion?() }) } else if let tab = self.tabManager.model.currentTab { // Favorites, etc if let image = viewCoordinator.contentContainer.createImageSnapshot() { previewsSource.update(preview: image, forTab: tab) + completion?() } + } else { + completion?() } } @@ -1157,7 +1162,7 @@ class MainViewController: UIViewController { viewCoordinator.toolbar.isHidden = false viewCoordinator.omniBar.enterPhoneState() - swipeTabsCoordinator?.isEnabled = featureFlagger.isFeatureOn(.swipeTabs) + swipeTabsCoordinator?.isEnabled = true } @discardableResult @@ -1291,7 +1296,7 @@ class MainViewController: UIViewController { } attachHomeScreen() tabsBarController?.refresh(tabsModel: tabManager.model) - swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) homeController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) } @@ -2249,16 +2254,11 @@ extension MainViewController: TabSwitcherButtonDelegate { guard let currentTab = currentTab ?? tabManager?.current(createIfNeeded: true) else { fatalError("Unable to get current tab") } - - currentTab.preparePreview(completion: { image in - if let image = image { - self.previewsSource.update(preview: image, - forTab: currentTab.tabModel) - } + updatePreviewForCurrentTab { ViewHighlighter.hideAll() self.segueToTabSwitcher() - }) + } } } diff --git a/DuckDuckGo/SwipeTabsCoordinator.swift b/DuckDuckGo/SwipeTabsCoordinator.swift index 180d9a17a0..d43a5a3ddb 100644 --- a/DuckDuckGo/SwipeTabsCoordinator.swift +++ b/DuckDuckGo/SwipeTabsCoordinator.swift @@ -73,6 +73,8 @@ class SwipeTabsCoordinator: NSObject { collectionView.dataSource = self collectionView.decelerationRate = .fast collectionView.backgroundColor = .clear + collectionView.showsHorizontalScrollIndicator = false + collectionView.showsVerticalScrollIndicator = false updateLayout() } @@ -92,7 +94,6 @@ class SwipeTabsCoordinator: NSObject { private func updateLayout() { let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout - layout?.scrollDirection = .horizontal layout?.itemSize = CGSize(width: coordinator.superview.frame.size.width, height: coordinator.omniBar.frame.height) layout?.minimumLineSpacing = 0 layout?.minimumInteritemSpacing = 0 @@ -101,7 +102,6 @@ class SwipeTabsCoordinator: NSObject { private func scrollToCurrent() { guard isEnabled else { return } - let targetOffset = collectionView.frame.width * CGFloat(tabsModel.currentIndex) guard targetOffset != collectionView.contentOffset.x else { @@ -118,12 +118,6 @@ class SwipeTabsCoordinator: NSObject { // MARK: UICollectionViewDelegate extension SwipeTabsCoordinator: UICollectionViewDelegate { - func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { - DispatchQueue.main.async { - self.scrollToCurrent() - } - } - func scrollViewDidScroll(_ scrollView: UIScrollView) { switch state { @@ -217,7 +211,7 @@ extension SwipeTabsCoordinator: UICollectionViewDelegate { switch state { case .idle: state = .starting(scrollView.contentOffset) - + default: break } } @@ -311,7 +305,8 @@ extension SwipeTabsCoordinator: UICollectionViewDataSource { if let url = tabsModel.safeGetTabAt(indexPath.row)?.link?.url { cell.omniBar?.startBrowsing() - cell.omniBar?.refreshText(forUrl: url) + cell.omniBar?.refreshText(forUrl: url, forceFullURL: appSettings.showFullSiteAddress) + cell.omniBar?.resetPrivacyIcon(for: url) } } diff --git a/DuckDuckGo/TabPreviewsSource.swift b/DuckDuckGo/TabPreviewsSource.swift index 2f58c6b468..7c287e14b7 100644 --- a/DuckDuckGo/TabPreviewsSource.swift +++ b/DuckDuckGo/TabPreviewsSource.swift @@ -51,11 +51,7 @@ class TabPreviewsSource { func update(preview: UIImage, forTab tab: Tab) { cache[tab.uid] = preview - - if tabSettings.isGridViewEnabled { - store(preview: preview, forTab: tab) - } - + store(preview: preview, forTab: tab) tab.didUpdatePreview() } From 32510976c1f2366abf0706e26fb517f71f7c1436 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 4 Apr 2024 17:06:42 +0200 Subject: [PATCH 198/245] Subscriptions. 28. Intercept Privacy Pro URL + Navigation Update Ship review fixes (#2670) Task/Issue URL: https://app.asana.com/0/0/1206983541370659/f Implements a URLInterceptor class to define custom actions when visiting specific URLs Other UI fixes based on Ship Review for navigation Updates Minor updates to cancellable removals and deInit cleanUp --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +++ DuckDuckGo/MainViewController+Segues.swift | 7 +- DuckDuckGo/MainViewController.swift | 18 +++- .../Extensions/View+AppearModifiers.swift | 23 ----- .../SubscriptionEmailViewModel.swift | 78 ++++++++++----- .../SubscriptionExternalLinkViewModel.swift | 9 +- .../ViewModel/SubscriptionFlowViewModel.swift | 52 ++++++++-- .../ViewModel/SubscriptionITPViewModel.swift | 8 +- .../SubscriptionRestoreViewModel.swift | 20 ++-- .../Views/DaxLogoNavbarTitle.swift | 39 ++++++++ .../Views/SubscriptionEmailView.swift | 16 ++- .../Views/SubscriptionFlowView.swift | 20 ++-- .../Views/SubscriptionITPView.swift | 10 +- .../Views/SubscriptionPIRView.swift | 10 +- .../Views/SubscriptionRestoreView.swift | 7 +- .../Views/SubscriptionSettingsView.swift | 4 +- DuckDuckGo/TabURLInterceptor.swift | 99 +++++++++++++++++++ DuckDuckGo/TabViewController.swift | 25 ++++- DuckDuckGoTests/TabURLInterceptorTests.swift | 65 ++++++++++++ 19 files changed, 408 insertions(+), 114 deletions(-) create mode 100644 DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift create mode 100644 DuckDuckGo/TabURLInterceptor.swift create mode 100644 DuckDuckGoTests/TabURLInterceptorTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5387560ef0..f537ec6d44 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -802,7 +802,9 @@ D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */; }; D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA152B7CF77300A0FBB9 /* Subscription */; }; D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */; }; + D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; + D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */; }; D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */; }; D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; }; D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */; }; @@ -828,6 +830,7 @@ D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; + D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */; }; D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; @@ -2476,7 +2479,9 @@ CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationURLDebugViewController.swift; sourceTree = ""; }; D60170BB2BA32DD6001911B5 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionGoogleView.swift; sourceTree = ""; }; + D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptorTests.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; + D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = ""; }; D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailView.swift; sourceTree = ""; }; D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailViewModel.swift; sourceTree = ""; }; D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsViewModel.swift; sourceTree = ""; }; @@ -2502,6 +2507,7 @@ D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; + D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptor.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; @@ -4677,6 +4683,7 @@ D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */, D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */, + D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */, ); path = Views; sourceTree = ""; @@ -5121,6 +5128,7 @@ B60DFF062872B64B0061E7C2 /* JSAlertController.swift */, B6BA95E728924730004ABA20 /* JSAlertController.storyboard */, 85010501292FB1000033978F /* FireproofFaviconUpdater.swift */, + D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */, ); name = UI; sourceTree = ""; @@ -5134,6 +5142,7 @@ F13B4BFA1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift */, F13B4BF81F18CA0600814661 /* TabsModelTests.swift */, F189AED61F18F6DE001EBAE1 /* TabTests.swift */, + D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */, ); name = Tabs; sourceTree = ""; @@ -6653,6 +6662,7 @@ C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, + D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, 020108A329A561C300644F9D /* AppTPActivityView.swift in Sources */, @@ -6918,6 +6928,7 @@ D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */, + D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */, C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, @@ -7060,6 +7071,7 @@ 85C11E4120904BBE00BFFEB4 /* VariantManagerTests.swift in Sources */, F1134ECE1F40EA9C00B73467 /* AtbParserTests.swift in Sources */, F189AEE41F18FDAF001EBAE1 /* LinkTests.swift in Sources */, + D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */, 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */, 858650D32469BFAD00C36F8A /* DaxDialogTests.swift in Sources */, 31C138B227A4097800FFD4B2 /* DownloadTestsHelper.swift in Sources */, diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 8b6299af34..7a36e77d1a 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -244,14 +244,17 @@ extension MainViewController { } } - private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { + func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil, + deepLinkTarget: SettingsViewModel.SettingsDeepLinkSection? = nil) { let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, syncDataProviders: syncDataProviders, appSettings: appSettings, bookmarksDatabase: bookmarksDatabase, tabManager: tabManager) #if SUBSCRIPTION - let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, accountManager: AccountManager()) + let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, + accountManager: AccountManager(), + deepLink: deepLinkTarget) #else let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider) #endif diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 3157597a2a..e144e49c99 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -107,6 +107,7 @@ class MainViewController: UIViewController { private var syncFeatureFlagsCancellable: AnyCancellable? private var favoritesDisplayModeCancellable: AnyCancellable? private var emailCancellables = Set() + private var urlInterceptorCancellables = Set() #if NETWORK_PROTECTION private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @@ -268,7 +269,8 @@ class MainViewController: UIViewController { previewsSource.prepare() addLaunchTabNotificationObserver() subscribeToEmailProtectionStatusNotifications() - + subscribeToURLInterceptorNotifications() + #if NETWORK_PROTECTION && SUBSCRIPTION subscribeToNetworkProtectionEvents() #endif @@ -1351,6 +1353,20 @@ class MainViewController: UIViewController { } .store(in: &emailCancellables) } + + private func subscribeToURLInterceptorNotifications() { + NotificationCenter.default.publisher(for: .urlInterceptPrivacyPro) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + switch notification.name { + case .urlInterceptPrivacyPro: + self?.launchSettings(deepLinkTarget: .subscriptionFlow) + default: + return + } + } + .store(in: &urlInterceptorCancellables) + } #if NETWORK_PROTECTION && SUBSCRIPTION private func subscribeToNetworkProtectionEvents() { diff --git a/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift b/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift index df8ce7bda2..bd7c78138d 100644 --- a/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift +++ b/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift @@ -38,33 +38,10 @@ public struct OnFirstAppearModifier: ViewModifier { } } -public struct OnFirstDisappearModifier: ViewModifier { - - private let onFirstDisappearAction: () -> Void - @State private var hasDisappeared = false - - public init(_ onFirstDisappearAction: @escaping () -> Void) { - self.onFirstDisappearAction = onFirstDisappearAction - } - - public func body(content: Content) -> some View { - content - .onDisappear { - guard !hasDisappeared else { return } - hasDisappeared = true - onFirstDisappearAction() - } - } -} - extension View { func onFirstAppear(_ onFirstAppearAction: @escaping () -> Void ) -> some View { return modifier(OnFirstAppearModifier(onFirstAppearAction)) } - func onFirstDisappear(_ onFirstDisappearAction: @escaping () -> Void ) -> some View { - return modifier(OnFirstDisappearModifier(onFirstDisappearAction)) - } - } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index dc46e7e153..bf101eed74 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -32,9 +32,9 @@ final class SubscriptionEmailViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature private var canGoBackCancellable: AnyCancellable? + private var urlCancellable: AnyCancellable? var emailURL = URL.activateSubscriptionViaEmail - var viewTitle = UserText.subscriptionActivateEmailTitle var webViewModel: AsyncHeadlessWebViewViewModel enum SelectedFeature { @@ -50,10 +50,12 @@ final class SubscriptionEmailViewModel: ObservableObject { var canNavigateBack: Bool = false var shouldDismissView: Bool = false var subscriptionActive: Bool = false + var isWelcomePageVisible: Bool = false var backButtonTitle: String = UserText.backButtonTitle var selectedFeature: SelectedFeature = .none var shouldPopToSubscriptionSettings: Bool = false var shouldPopToAppSettings: Bool = false + var viewTitle = UserText.subscriptionActivateEmailTitle } // Read only View State - Should only be modified from the VM @@ -68,6 +70,11 @@ final class SubscriptionEmailViewModel: ObservableObject { } private var cancellables = Set() + + private var isWelcomePageOrSuccessPage: Bool { + webViewModel.url?.forComparison() == URL.subscriptionActivateSuccess.forComparison() || + webViewModel.url?.forComparison() == URL.subscriptionPurchase.forComparison() + } init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, @@ -103,33 +110,34 @@ final class SubscriptionEmailViewModel: ObservableObject { @MainActor func onFirstAppear() { - setupObservers() - if accountManager.isUserAuthenticated { + setupWebObservers() + setupFeatureObservers() + } + + private func cleanUp() { + canGoBackCancellable?.cancel() + subFeature.cleanup() + cancellables.removeAll() + } + + func onAppear() { + state.shouldDismissView = false + // If the user is Authenticated & not in the Welcome page + if accountManager.isUserAuthenticated && !isWelcomePageOrSuccessPage { // If user is authenticated, we want to "Add or manage email" instead of activating emailURL = accountManager.email == nil ? URL.addEmailToSubscription : URL.manageSubscriptionEmail - viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle + state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle // Also we assume subscription requires managing, and not activation state.managingSubscriptionEmail = true } - if webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess { + // Load the Email Management URL unless the user has activated a subscription or is on the welcome page + if !isWelcomePageOrSuccessPage { self.webViewModel.navigationCoordinator.navigateTo(url: self.emailURL) } } - func onFirstDisappear() { - cancellables.removeAll() - canGoBackCancellable = nil - } - - private func setupObservers() { - - // Webview navigation - canGoBackCancellable = webViewModel.$canGoBack - .receive(on: DispatchQueue.main) - .sink { [weak self] value in - self?.updateBackButton(canNavigateBack: value) - } + private func setupFeatureObservers() { // Feature Callback subFeature.onSetSubscription = { @@ -172,7 +180,26 @@ final class SubscriptionEmailViewModel: ObservableObject { } } .store(in: &cancellables) - + } + + private func setupWebObservers() { + + // Webview navigation + canGoBackCancellable = webViewModel.$canGoBack + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.updateBackButton(canNavigateBack: value) + } + + // Webview navigation + urlCancellable = webViewModel.$url + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + if self?.isWelcomePageOrSuccessPage ?? false { + self?.state.viewTitle = UserText.subscriptionTitle + } + } + webViewModel.$navigationError .receive(on: DispatchQueue.main) .sink { [weak self] error in @@ -185,17 +212,14 @@ final class SubscriptionEmailViewModel: ObservableObject { .store(in: &cancellables) } - func updateBackButton(canNavigateBack: Bool) { - - // Disable Browser navigation by default - self.state.canNavigateBack = false + private func updateBackButton(canNavigateBack: Bool) { // If the view is not Activation Success, or Welcome page, allow WebView Back Navigation - if self.webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess.forComparison() && - self.webViewModel.url?.forComparison() != URL.subscriptionPurchase.forComparison() { + if !isWelcomePageOrSuccessPage { self.state.canNavigateBack = canNavigateBack self.state.backButtonTitle = UserText.backButtonTitle } else { + self.state.canNavigateBack = false self.state.backButtonTitle = UserText.settingsTitle } @@ -228,9 +252,9 @@ final class SubscriptionEmailViewModel: ObservableObject { } deinit { - cancellables.removeAll() + cleanUp() canGoBackCancellable = nil - + } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift index f2f5feb45d..4890437265 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift @@ -56,7 +56,11 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { func onFirstAppear() { Task { await setupSubscribers() } webViewModel.navigationCoordinator.navigateTo(url: url) - + } + + private func cleanUp() { + canGoBackCancellable?.cancel() + cancellables.removeAll() } @MainActor @@ -65,7 +69,8 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { } deinit { - cancellables.removeAll() + cleanUp() + canGoBackCancellable = nil } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 518a9cd0e9..ec8e188e07 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -32,12 +32,12 @@ final class SubscriptionFlowViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature let purchaseManager: PurchaseManager var webViewModel: AsyncHeadlessWebViewViewModel - - let viewTitle = UserText.settingsPProSection + var purchaseURL = URL.subscriptionPurchase private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? + private var urlCancellable: AnyCancellable? enum Constants { static let navigationBarHideThreshold = 80.0 @@ -56,6 +56,7 @@ final class SubscriptionFlowViewModel: ObservableObject { var transactionError: SubscriptionPurchaseError? var shouldHideBackButton = false var selectedFeature: SelectedFeature = .none + var viewTitle: String = UserText.subscriptionTitle } // Read only View State - Should only be modified from the VM @@ -217,10 +218,25 @@ final class SubscriptionFlowViewModel: ObservableObject { } } } + + urlCancellable = webViewModel.$url + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.state.canNavigateBack = false + guard let currentURL = self?.webViewModel.url else { return } + if currentURL.forComparison() == URL.addEmailToSubscription.forComparison() || + currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() || + currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() { + strongSelf.state.viewTitle = UserText.subscriptionRestoreAddEmailTitle + } else { + strongSelf.state.viewTitle = UserText.subscriptionTitle + } + } + } private func backButtonForURL(currentURL: URL) -> Bool { - print(currentURL) return currentURL.forComparison() != URL.subscriptionBaseURL.forComparison() && currentURL.forComparison() != URL.subscriptionActivateSuccess.forComparison() && currentURL.forComparison() != URL.subscriptionPurchase.forComparison() @@ -228,6 +244,7 @@ final class SubscriptionFlowViewModel: ObservableObject { private func cleanUp() { canGoBackCancellable?.cancel() + urlCancellable?.cancel() subFeature.cleanup() cancellables.removeAll() } @@ -239,6 +256,8 @@ final class SubscriptionFlowViewModel: ObservableObject { deinit { cleanUp() + canGoBackCancellable = nil + urlCancellable = nil } @MainActor @@ -253,21 +272,21 @@ final class SubscriptionFlowViewModel: ObservableObject { // MARK: - + func onAppear() { + self.state.selectedFeature = .none + } + func onFirstAppear() async { DispatchQueue.main.async { self.resetState() } - await self.setupTransactionObserver() - await self .setupWebViewObservers() - if webViewModel.url == nil { + if webViewModel.url != URL.subscriptionPurchase.forComparison() { self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL) } + await self.setupTransactionObserver() + await self.setupWebViewObservers() Pixel.fire(pixel: .privacyProOfferScreenImpression) } - - func onFirstDisappear() async { - cleanUp() - } @MainActor func restoreAppstoreTransaction() { @@ -298,3 +317,16 @@ final class SubscriptionFlowViewModel: ObservableObject { } #endif + +// TODO: Move to BSK later +private extension URL { + + static var addEmailToSubscriptionSuccess: URL { + subscriptionBaseURL.appendingPathComponent("add-email/success") + } + + static var addEmailToSubscriptionOTP: URL { + subscriptionBaseURL.appendingPathComponent("add-email/otp") + } + +} diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 6efa74f56d..4ff55c52a7 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -139,6 +139,11 @@ final class SubscriptionITPViewModel: ObservableObject { Pixel.fire(pixel: .privacyProIdentityRestorationSettings) } + private func cleanUp() { + canGoBackCancellable?.cancel() + cancellables.removeAll() + } + private func downloadAttachment(from url: URL) async { if let (temporaryURL, _) = try? await URLSession.shared.download(from: url) { let fileManager = FileManager.default @@ -181,7 +186,8 @@ final class SubscriptionITPViewModel: ObservableObject { } deinit { - cancellables.removeAll() + cleanUp() + canGoBackCancellable = nil self.userScript = nil self.subFeature = nil } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 3a56f4cc29..8d1482f65c 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -69,28 +69,30 @@ final class SubscriptionRestoreViewModel: ObservableObject { self.state.isAddingDevice = false } - func onFirstAppear() async { + func onAppear() { DispatchQueue.main.async { self.resetState() } - await setupContent() - await setupTransactionObserver() + Task { await setupContent() } } - - func onFirstDisappear() async { - cleanUp() + + func onFirstAppear() async { + Pixel.fire(pixel: .privacyProSettingsAddDevice) + await setupTransactionObserver() } private func cleanUp() { + subFeature.cleanup() cancellables.removeAll() } + private func setupContent() async { if state.isAddingDevice { DispatchQueue.main.async { self.state.isLoading = true } - Pixel.fire(pixel: .privacyProSettingsAddDevice) + guard let token = accountManager.accessToken else { return } switch await accountManager.fetchAccountDetails(with: token) { case .success(let details): @@ -198,6 +200,10 @@ final class SubscriptionRestoreViewModel: ObservableObject { state.shouldDismissView = true } + deinit { + cleanUp() + } + } #endif diff --git a/DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift b/DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift new file mode 100644 index 0000000000..b6e7a149c3 --- /dev/null +++ b/DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift @@ -0,0 +1,39 @@ +// +// DaxLogoNavbarTitle.swift +// DuckDuckGo +// +// 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 DaxLogoNavbarTitle: View { + + enum Constants { + static let daxLogoSize: CGFloat = 24.0 + static let daxLogo = "Home" + } + + var body: some View { + HStack { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(UserText.subscriptionTitle).daxBodyRegular() + } + } + +} diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 6fc62f7643..ce23b945ea 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -60,6 +60,9 @@ struct SubscriptionEmailView: View { ToolbarItemGroup(placement: .navigationBarLeading) { browserBackButton } + ToolbarItemGroup(placement: .principal) { + daxLogoToolbarItem + } } .navigationBarTitleDisplayMode(.inline) .navigationViewStyle(.stack) @@ -126,13 +129,17 @@ struct SubscriptionEmailView: View { } } - .navigationTitle(viewModel.viewTitle) + .navigationTitle(viewModel.state.viewTitle) .onFirstAppear { setUpAppearances() viewModel.onFirstAppear() } + .onAppear { + viewModel.onAppear() + } + } // MARK: - @@ -158,6 +165,13 @@ struct SubscriptionEmailView: View { }) } + @ViewBuilder + private var daxLogoToolbarItem: some View { + if viewModel.state.viewTitle == UserText.subscriptionTitle { + DaxLogoNavbarTitle() + } + } + private func setUpAppearances() { let navAppearance = UINavigationBar.appearance() navAppearance.backgroundColor = UIColor(designSystemColor: .surface) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 3f8d3fe006..23f206d79f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -41,8 +41,6 @@ struct SubscriptionFlowView: View { @State private var isPresentingError: Bool = false enum Constants { - static let daxLogo = "Home" - static let daxLogoSize: CGFloat = 24.0 static let empty = "" static let navButtonPadding: CGFloat = 20.0 static let backButtonImage = "chevron.left" @@ -74,12 +72,10 @@ struct SubscriptionFlowView: View { backButton } ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() + if viewModel.state.viewTitle == UserText.subscriptionTitle { + DaxLogoNavbarTitle() + } else { + Text(viewModel.state.viewTitle).bold() } } } @@ -175,13 +171,9 @@ struct SubscriptionFlowView: View { setUpAppearances() Task { await viewModel.onFirstAppear() } } - - .onFirstDisappear { - Task { await viewModel.onFirstDisappear() } - } - + .onAppear { - Task { await viewModel.onFirstAppear() } + viewModel.onAppear() } .alert(isPresented: $isPresentingError) { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 5904854dc2..490d68b1fb 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -42,8 +42,6 @@ struct SubscriptionITPView: View { @State private var isShowingActivityView = false enum Constants { - static let daxLogo = "Home" - static let daxLogoSize: CGFloat = 24.0 static let empty = "" static let navButtonPadding: CGFloat = 20.0 static let backButtonImage = "chevron.left" @@ -59,13 +57,7 @@ struct SubscriptionITPView: View { backButton } ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() - } + DaxLogoNavbarTitle() } ToolbarItem(placement: .navigationBarTrailing) { shareButton diff --git a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift index 9756c040ca..b6f97b1afe 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift @@ -33,8 +33,6 @@ struct SubscriptionPIRView: View { @State private var isShowingMacView = false enum Constants { - static let daxLogo = "Home" - static let daxLogoSize: CGFloat = 24.0 static let empty = "" static let navButtonPadding: CGFloat = 20.0 static let lightMask: [Color] = [Color.init(0xFFFFFF, alpha: 0), Color.init(0xFFFFFF, alpha: 0)] @@ -62,13 +60,7 @@ struct SubscriptionPIRView: View { } .toolbar { ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() - } + DaxLogoNavbarTitle() } } .onFirstAppear { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index f010b8aef0..a51a5d6876 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -151,10 +151,11 @@ struct SubscriptionRestoreView: View { Task { await viewModel.onFirstAppear() } setUpAppearances() } + + .onAppear { + viewModel.onAppear() + } - .onFirstDisappear { - Task { await viewModel.onFirstDisappear() } - } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index e93ca2e55f..f3bf14d671 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -38,9 +38,9 @@ struct SubscriptionSettingsView: View { var body: some View { optionsView - .onAppear(perform: { + .onFirstAppear { Pixel.fire(pixel: .privacyProSubscriptionSettings, debounce: 1) - }) + } .navigationBarTitleDisplayMode(.inline) } diff --git a/DuckDuckGo/TabURLInterceptor.swift b/DuckDuckGo/TabURLInterceptor.swift new file mode 100644 index 0000000000..600b18e2fb --- /dev/null +++ b/DuckDuckGo/TabURLInterceptor.swift @@ -0,0 +1,99 @@ +// +// TabURLInterceptor.swift +// DuckDuckGo +// +// 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 Common +import Subscription + +enum InterceptedURL: String { + case privacyPro +} + +struct InterceptedURLInfo { + let id: InterceptedURL + let path: String +} + +protocol TabURLInterceptor { + func allowsNavigatingTo(url: URL) -> Bool +} + +final class TabURLInterceptorDefault: TabURLInterceptor { + + + static let interceptedURLs: [InterceptedURLInfo] = [ + InterceptedURLInfo(id: .privacyPro, path: "/pro") + ] + + func allowsNavigatingTo(url: URL) -> Bool { + + if !url.isPart(ofDomain: "duckduckgo.com") { + return true + } + + guard let components = normalizeScheme(url.absoluteString) else { + return true + } + + guard let matchingURL = urlToIntercept(path: components.path) else { + return true + } + + return Self.handleURLInterception(url: matchingURL.id) + + } +} + +extension TabURLInterceptorDefault { + + private func urlToIntercept(path: String) -> InterceptedURLInfo? { + let results = Self.interceptedURLs.filter { $0.path == path } + return results.first + } + + private func normalizeScheme(_ rawUrl: String) -> URLComponents? { + if !rawUrl.starts(with: URL.URLProtocol.https.scheme) && + !rawUrl.starts(with: URL.URLProtocol.http.scheme) && + rawUrl.contains("://") { + return nil + } + let noScheme = rawUrl.dropping(prefix: URL.URLProtocol.https.scheme).dropping(prefix: URL.URLProtocol.http.scheme) + + return URLComponents(string: "\(URL.URLProtocol.https.scheme)\(noScheme)") + } + + private static func handleURLInterception(url: InterceptedURL) -> Bool { + switch url { + + // Opens the Privacy Pro Subscription Purchase page (if user can purchase) + case .privacyPro: + if SubscriptionPurchaseEnvironment.canPurchase { + NotificationCenter.default.post(name: .urlInterceptPrivacyPro, object: nil) + return false + } + } + return true + + } +} + +extension NSNotification.Name { + static let urlInterceptPrivacyPro: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.urlInterceptPrivacyPro") +} diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 5429c52948..72ab21f196 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -127,6 +127,9 @@ class TabViewController: UIViewController { private var trackersInfoWorkItem: DispatchWorkItem? + private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault() + private var currentlyLoadedURL: URL? + #if NETWORK_PROTECTION private let netPConnectionObserver = ConnectionStatusObserverThroughSession() private var netPConnectionObserverCancellable: AnyCancellable? @@ -284,6 +287,8 @@ class TabViewController: UIViewController { } private let rulesCompilationMonitor = RulesCompilationMonitor.shared + + private var lastRenderedURL: URL? static func loadFromStoryboard(model: Tab, appSettings: AppSettings = AppDependencyProvider.shared.appSettings, @@ -1092,7 +1097,7 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { - + let mimeType = MIMEType(from: navigationResponse.response.mimeType) let httpResponse = navigationResponse.response as? HTTPURLResponse @@ -1154,6 +1159,7 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { lastError = nil + lastRenderedURL = webView.url cancelTrackerNetworksAnimation() shouldReloadOnError = false hideErrorMessage() @@ -1165,7 +1171,7 @@ extension TabViewController: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - + self.currentlyLoadedURL = webView.url adClickAttributionDetection.onDidFinishNavigation(url: webView.url) adClickAttributionLogic.onDidFinishNavigation(host: webView.url?.host) hideProgressIndicator() @@ -1394,7 +1400,20 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - + + if let url = navigationAction.request.url { + if !tabURLInterceptor.allowsNavigatingTo(url: url) { + decisionHandler(.cancel) + // If there is history or a page loaded keep the tab open + if self.currentlyLoadedURL != nil { + refresh() + } else { + delegate?.tabDidRequestClose(self) + } + return + } + } + if let url = navigationAction.request.url, !url.isDuckDuckGoSearch, true == shouldWaitUntilContentBlockingIsLoaded({ [weak self, webView /* decision handler must be called */] in diff --git a/DuckDuckGoTests/TabURLInterceptorTests.swift b/DuckDuckGoTests/TabURLInterceptorTests.swift new file mode 100644 index 0000000000..b96e78ed9e --- /dev/null +++ b/DuckDuckGoTests/TabURLInterceptorTests.swift @@ -0,0 +1,65 @@ +// +// TabURLInterceptorTests.swift +// DuckDuckGo +// +// 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 XCTest +import Subscription +@testable import DuckDuckGo + +class TabURLInterceptorDefaultTests: XCTestCase { + + var urlInterceptor: TabURLInterceptorDefault! + + override func setUp() { + super.setUp() + // Simulate purchase allowance + SubscriptionPurchaseEnvironment.canPurchase = true + urlInterceptor = TabURLInterceptorDefault() + } + + override func tearDown() { + urlInterceptor = nil + super.tearDown() + } + + func testAllowsNavigationForNonDuckDuckGoDomain() { + let url = URL(string: "https://www.example.com")! + XCTAssertTrue(urlInterceptor.allowsNavigatingTo(url: url)) + } + + func testAllowsNavigationForUninterceptedDuckDuckGoPath() { + let url = URL(string: "https://duckduckgo.com/about")! + XCTAssertTrue(urlInterceptor.allowsNavigatingTo(url: url)) + } + + func testNotificationForInterceptedPrivacyProPath() { + let expectation = self.expectation(forNotification: .urlInterceptPrivacyPro, object: nil, handler: nil) + + let url = URL(string: "https://duckduckgo.com/pro")! + let canNavigate = urlInterceptor.allowsNavigatingTo(url: url) + + // Fail if no note is posted + XCTAssertFalse(canNavigate) + + waitForExpectations(timeout: 1) { error in + if let error = error { + XCTFail("Notification expectation failed: \(error)") + } + } + } +} From 78902d33c282d76e3edb82e7093ecdbb20aedf49 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 4 Apr 2024 08:41:59 -0700 Subject: [PATCH 199/245] [Release PR] Fix waitlist beta metadata check (#2674) Task/Issue URL: https://app.asana.com/0/414235014887631/1207001617932318/f Tech Design URL: CC: Description: This PR changes the check we use to tell if someone is a beta user. --- DuckDuckGo/Feedback/VPNMetadataCollector.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index 3fb97ffc52..cc3b47bf0a 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -289,7 +289,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { return .init( enableSource: .init(from: accessManager.networkProtectionAccessType()), - betaParticipant: accessType == .waitlistJoined, + betaParticipant: accessType == .waitlistInvited, hasToken: hasToken, subscriptionActive: AccountManager(subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)).isUserAuthenticated ) From 5bf1fbf250357d720e2bafcd318f5b1f9295ae8f Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Thu, 4 Apr 2024 12:00:25 -0400 Subject: [PATCH 200/245] Remove background task for waitlist (#2675) Co-authored-by: Sam Symons --- DuckDuckGo/AppDelegate+Waitlists.swift | 16 ---------------- DuckDuckGo/AppDelegate.swift | 6 ------ DuckDuckGo/Info.plist | 1 - DuckDuckGo/VPNWaitlistViewController.swift | 2 +- 4 files changed, 1 insertion(+), 24 deletions(-) diff --git a/DuckDuckGo/AppDelegate+Waitlists.swift b/DuckDuckGo/AppDelegate+Waitlists.swift index 4f46ee1f3c..361c2e2ec3 100644 --- a/DuckDuckGo/AppDelegate+Waitlists.swift +++ b/DuckDuckGo/AppDelegate+Waitlists.swift @@ -41,8 +41,6 @@ extension AppDelegate { checkNetworkProtectionWaitlist() } #endif - checkWaitlistBackgroundTasks() - } #if NETWORK_PROTECTION @@ -66,20 +64,6 @@ extension AppDelegate { } #endif - private func checkWaitlistBackgroundTasks() { - guard vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() else { return } - - BGTaskScheduler.shared.getPendingTaskRequests { tasks in - -#if NETWORK_PROTECTION - let hasVPNWaitlistTask = tasks.contains { $0.identifier == VPNWaitlist.backgroundRefreshTaskIdentifier } - if !hasVPNWaitlistTask { - VPNWaitlist.shared.scheduleBackgroundRefreshTask() - } -#endif - } - } - #if NETWORK_PROTECTION func fetchVPNWaitlistAuthToken(inviteCode: String) { Task { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 42def30fc4..d6a15bc380 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -303,12 +303,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Having both in `didBecomeActive` can sometimes cause the exception when running on a physical device, so registration happens here. AppConfigurationFetch.registerBackgroundRefreshTaskHandler() -#if NETWORK_PROTECTION - if vpnFeatureVisibility.shouldKeepVPNAccessViaWaitlist() { - VPNWaitlist.shared.registerBackgroundRefreshTaskHandler() - } -#endif - RemoteMessaging.registerBackgroundRefreshTaskHandler( bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: AppDependencyProvider.shared.appSettings.favoritesDisplayMode diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index e5d5328d08..86ef7b8d64 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -4,7 +4,6 @@ BGTaskSchedulerPermittedIdentifiers - com.duckduckgo.app.vpnWaitlistStatus com.duckduckgo.app.configurationRefresh com.duckduckgo.app.remoteMessageRefresh diff --git a/DuckDuckGo/VPNWaitlistViewController.swift b/DuckDuckGo/VPNWaitlistViewController.swift index 8fa13949e2..38c4397af2 100644 --- a/DuckDuckGo/VPNWaitlistViewController.swift +++ b/DuckDuckGo/VPNWaitlistViewController.swift @@ -120,7 +120,7 @@ extension VPNWaitlistViewController: WaitlistViewModelDelegate { } func waitlistViewModelDidJoinQueueWithNotificationsAllowed(_ viewModel: WaitlistViewModel) { - VPNWaitlist.shared.scheduleBackgroundRefreshTask() + // no-op } func waitlistViewModel(_ viewModel: WaitlistViewModel, didTriggerCustomAction action: WaitlistViewModel.ViewCustomAction) { From 1cd3e969197c6a54100fe169cbbe5c0a77f57821 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 4 Apr 2024 20:08:31 +0200 Subject: [PATCH 201/245] Subscription-related updates (#2676) Description: Cherry-picking subscription-related updates from main --- DuckDuckGo.xcodeproj/project.pbxproj | 12 +++ DuckDuckGo/MainViewController+Segues.swift | 7 +- DuckDuckGo/MainViewController.swift | 18 +++- .../Extensions/View+AppearModifiers.swift | 23 ----- .../SubscriptionEmailViewModel.swift | 78 ++++++++++----- .../SubscriptionExternalLinkViewModel.swift | 9 +- .../ViewModel/SubscriptionFlowViewModel.swift | 52 ++++++++-- .../ViewModel/SubscriptionITPViewModel.swift | 8 +- .../SubscriptionRestoreViewModel.swift | 20 ++-- .../Views/DaxLogoNavbarTitle.swift | 39 ++++++++ .../Views/SubscriptionEmailView.swift | 16 ++- .../Views/SubscriptionFlowView.swift | 20 ++-- .../Views/SubscriptionITPView.swift | 10 +- .../Views/SubscriptionPIRView.swift | 10 +- .../Views/SubscriptionRestoreView.swift | 7 +- .../Views/SubscriptionSettingsView.swift | 4 +- DuckDuckGo/TabURLInterceptor.swift | 99 +++++++++++++++++++ DuckDuckGo/TabViewController.swift | 25 ++++- DuckDuckGoTests/TabURLInterceptorTests.swift | 65 ++++++++++++ 19 files changed, 408 insertions(+), 114 deletions(-) create mode 100644 DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift create mode 100644 DuckDuckGo/TabURLInterceptor.swift create mode 100644 DuckDuckGoTests/TabURLInterceptorTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 5387560ef0..f537ec6d44 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -802,7 +802,9 @@ D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */; }; D61CDA162B7CF77300A0FBB9 /* Subscription in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA152B7CF77300A0FBB9 /* Subscription */; }; D61CDA182B7CF78300A0FBB9 /* ZIPFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = D61CDA172B7CF78300A0FBB9 /* ZIPFoundation */; }; + D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */; }; D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */; }; + D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */; }; D64648AD2B59936B0033090B /* SubscriptionEmailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */; }; D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; }; D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */; }; @@ -828,6 +830,7 @@ D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; D68DF81E2B5830380023DBEA /* SubscriptionRestoreViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */; }; D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */; }; + D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */; }; D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */; }; D6BFCB612B7525160051FF81 /* SubscriptionPIRViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */; }; D6D95CE32B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */; }; @@ -2476,7 +2479,9 @@ CBFCB30D2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationURLDebugViewController.swift; sourceTree = ""; }; D60170BB2BA32DD6001911B5 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionGoogleView.swift; sourceTree = ""; }; + D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptorTests.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; + D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = ""; }; D64648AC2B59936B0033090B /* SubscriptionEmailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailView.swift; sourceTree = ""; }; D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionEmailViewModel.swift; sourceTree = ""; }; D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionSettingsViewModel.swift; sourceTree = ""; }; @@ -2502,6 +2507,7 @@ D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreViewModel.swift; sourceTree = ""; }; D69FBF752B28BE3600B505F1 /* SettingsSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSubscriptionView.swift; sourceTree = ""; }; + D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptor.swift; sourceTree = ""; }; D6BFCB5E2B7524AA0051FF81 /* SubscriptionPIRView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRView.swift; sourceTree = ""; }; D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPIRViewModel.swift; sourceTree = ""; }; D6D95CE22B6D9F8800960317 /* AsyncHeadlessWebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncHeadlessWebViewModel.swift; sourceTree = ""; }; @@ -4677,6 +4683,7 @@ D6F93E3D2B50A8A0004C268D /* SubscriptionSettingsView.swift */, D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */, D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */, + D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */, ); path = Views; sourceTree = ""; @@ -5121,6 +5128,7 @@ B60DFF062872B64B0061E7C2 /* JSAlertController.swift */, B6BA95E728924730004ABA20 /* JSAlertController.storyboard */, 85010501292FB1000033978F /* FireproofFaviconUpdater.swift */, + D6ACEA312BBD55BF008FADDF /* TabURLInterceptor.swift */, ); name = UI; sourceTree = ""; @@ -5134,6 +5142,7 @@ F13B4BFA1F18E3D900814661 /* TabsModelPersistenceExtensionTests.swift */, F13B4BF81F18CA0600814661 /* TabsModelTests.swift */, F189AED61F18F6DE001EBAE1 /* TabTests.swift */, + D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */, ); name = Tabs; sourceTree = ""; @@ -6653,6 +6662,7 @@ C1D21E2D293A5965006E5A05 /* AutofillLoginSession.swift in Sources */, 4B53648A26718D0E001AA041 /* EmailWaitlist.swift in Sources */, 027F48762A4B5FBE001A1C6C /* AppTPLinkButton.swift in Sources */, + D63677F52BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift in Sources */, 8524CC98246D66E100E59D45 /* String+Markdown.swift in Sources */, CBEFB9142AE0844700DEDE7B /* CriticalAlerts.swift in Sources */, 020108A329A561C300644F9D /* AppTPActivityView.swift in Sources */, @@ -6918,6 +6928,7 @@ D6E83C562B21ECC1006C8AFB /* SettingsLegacyViewProvider.swift in Sources */, 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */, D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */, + D6ACEA322BBD55BF008FADDF /* TabURLInterceptor.swift in Sources */, C13F3F6A2B7F883A0083BE40 /* AuthConfirmationPromptViewController.swift in Sources */, 02C57C4B2514FEFB009E5129 /* DoNotSellSettingsViewController.swift in Sources */, 02A54A9C2A097C95000C8FED /* AppTPHomeViewSectionRenderer.swift in Sources */, @@ -7060,6 +7071,7 @@ 85C11E4120904BBE00BFFEB4 /* VariantManagerTests.swift in Sources */, F1134ECE1F40EA9C00B73467 /* AtbParserTests.swift in Sources */, F189AEE41F18FDAF001EBAE1 /* LinkTests.swift in Sources */, + D625AAEC2BBEF27600BC189A /* TabURLInterceptorTests.swift in Sources */, 987130C7294AAB9F00AB05E0 /* MenuBookmarksViewModelTests.swift in Sources */, 858650D32469BFAD00C36F8A /* DaxDialogTests.swift in Sources */, 31C138B227A4097800FFD4B2 /* DownloadTestsHelper.swift in Sources */, diff --git a/DuckDuckGo/MainViewController+Segues.swift b/DuckDuckGo/MainViewController+Segues.swift index 8b6299af34..7a36e77d1a 100644 --- a/DuckDuckGo/MainViewController+Segues.swift +++ b/DuckDuckGo/MainViewController+Segues.swift @@ -244,14 +244,17 @@ extension MainViewController { } } - private func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil) { + func launchSettings(completion: ((SettingsViewModel) -> Void)? = nil, + deepLinkTarget: SettingsViewModel.SettingsDeepLinkSection? = nil) { let legacyViewProvider = SettingsLegacyViewProvider(syncService: syncService, syncDataProviders: syncDataProviders, appSettings: appSettings, bookmarksDatabase: bookmarksDatabase, tabManager: tabManager) #if SUBSCRIPTION - let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, accountManager: AccountManager()) + let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider, + accountManager: AccountManager(), + deepLink: deepLinkTarget) #else let settingsViewModel = SettingsViewModel(legacyViewProvider: legacyViewProvider) #endif diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 92c0277371..cfa28d4ed3 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -107,6 +107,7 @@ class MainViewController: UIViewController { private var syncFeatureFlagsCancellable: AnyCancellable? private var favoritesDisplayModeCancellable: AnyCancellable? private var emailCancellables = Set() + private var urlInterceptorCancellables = Set() #if NETWORK_PROTECTION private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @@ -268,7 +269,8 @@ class MainViewController: UIViewController { previewsSource.prepare() addLaunchTabNotificationObserver() subscribeToEmailProtectionStatusNotifications() - + subscribeToURLInterceptorNotifications() + #if NETWORK_PROTECTION && SUBSCRIPTION subscribeToNetworkProtectionEvents() #endif @@ -1346,6 +1348,20 @@ class MainViewController: UIViewController { } .store(in: &emailCancellables) } + + private func subscribeToURLInterceptorNotifications() { + NotificationCenter.default.publisher(for: .urlInterceptPrivacyPro) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + switch notification.name { + case .urlInterceptPrivacyPro: + self?.launchSettings(deepLinkTarget: .subscriptionFlow) + default: + return + } + } + .store(in: &urlInterceptorCancellables) + } #if NETWORK_PROTECTION && SUBSCRIPTION private func subscribeToNetworkProtectionEvents() { diff --git a/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift b/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift index df8ce7bda2..bd7c78138d 100644 --- a/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift +++ b/DuckDuckGo/Subscription/Extensions/View+AppearModifiers.swift @@ -38,33 +38,10 @@ public struct OnFirstAppearModifier: ViewModifier { } } -public struct OnFirstDisappearModifier: ViewModifier { - - private let onFirstDisappearAction: () -> Void - @State private var hasDisappeared = false - - public init(_ onFirstDisappearAction: @escaping () -> Void) { - self.onFirstDisappearAction = onFirstDisappearAction - } - - public func body(content: Content) -> some View { - content - .onDisappear { - guard !hasDisappeared else { return } - hasDisappeared = true - onFirstDisappearAction() - } - } -} - extension View { func onFirstAppear(_ onFirstAppearAction: @escaping () -> Void ) -> some View { return modifier(OnFirstAppearModifier(onFirstAppearAction)) } - func onFirstDisappear(_ onFirstDisappearAction: @escaping () -> Void ) -> some View { - return modifier(OnFirstDisappearModifier(onFirstDisappearAction)) - } - } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index dc46e7e153..bf101eed74 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -32,9 +32,9 @@ final class SubscriptionEmailViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature private var canGoBackCancellable: AnyCancellable? + private var urlCancellable: AnyCancellable? var emailURL = URL.activateSubscriptionViaEmail - var viewTitle = UserText.subscriptionActivateEmailTitle var webViewModel: AsyncHeadlessWebViewViewModel enum SelectedFeature { @@ -50,10 +50,12 @@ final class SubscriptionEmailViewModel: ObservableObject { var canNavigateBack: Bool = false var shouldDismissView: Bool = false var subscriptionActive: Bool = false + var isWelcomePageVisible: Bool = false var backButtonTitle: String = UserText.backButtonTitle var selectedFeature: SelectedFeature = .none var shouldPopToSubscriptionSettings: Bool = false var shouldPopToAppSettings: Bool = false + var viewTitle = UserText.subscriptionActivateEmailTitle } // Read only View State - Should only be modified from the VM @@ -68,6 +70,11 @@ final class SubscriptionEmailViewModel: ObservableObject { } private var cancellables = Set() + + private var isWelcomePageOrSuccessPage: Bool { + webViewModel.url?.forComparison() == URL.subscriptionActivateSuccess.forComparison() || + webViewModel.url?.forComparison() == URL.subscriptionPurchase.forComparison() + } init(userScript: SubscriptionPagesUserScript, subFeature: SubscriptionPagesUseSubscriptionFeature, @@ -103,33 +110,34 @@ final class SubscriptionEmailViewModel: ObservableObject { @MainActor func onFirstAppear() { - setupObservers() - if accountManager.isUserAuthenticated { + setupWebObservers() + setupFeatureObservers() + } + + private func cleanUp() { + canGoBackCancellable?.cancel() + subFeature.cleanup() + cancellables.removeAll() + } + + func onAppear() { + state.shouldDismissView = false + // If the user is Authenticated & not in the Welcome page + if accountManager.isUserAuthenticated && !isWelcomePageOrSuccessPage { // If user is authenticated, we want to "Add or manage email" instead of activating emailURL = accountManager.email == nil ? URL.addEmailToSubscription : URL.manageSubscriptionEmail - viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle + state.viewTitle = accountManager.email == nil ? UserText.subscriptionRestoreAddEmailTitle : UserText.subscriptionManageEmailTitle // Also we assume subscription requires managing, and not activation state.managingSubscriptionEmail = true } - if webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess { + // Load the Email Management URL unless the user has activated a subscription or is on the welcome page + if !isWelcomePageOrSuccessPage { self.webViewModel.navigationCoordinator.navigateTo(url: self.emailURL) } } - func onFirstDisappear() { - cancellables.removeAll() - canGoBackCancellable = nil - } - - private func setupObservers() { - - // Webview navigation - canGoBackCancellable = webViewModel.$canGoBack - .receive(on: DispatchQueue.main) - .sink { [weak self] value in - self?.updateBackButton(canNavigateBack: value) - } + private func setupFeatureObservers() { // Feature Callback subFeature.onSetSubscription = { @@ -172,7 +180,26 @@ final class SubscriptionEmailViewModel: ObservableObject { } } .store(in: &cancellables) - + } + + private func setupWebObservers() { + + // Webview navigation + canGoBackCancellable = webViewModel.$canGoBack + .receive(on: DispatchQueue.main) + .sink { [weak self] value in + self?.updateBackButton(canNavigateBack: value) + } + + // Webview navigation + urlCancellable = webViewModel.$url + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + if self?.isWelcomePageOrSuccessPage ?? false { + self?.state.viewTitle = UserText.subscriptionTitle + } + } + webViewModel.$navigationError .receive(on: DispatchQueue.main) .sink { [weak self] error in @@ -185,17 +212,14 @@ final class SubscriptionEmailViewModel: ObservableObject { .store(in: &cancellables) } - func updateBackButton(canNavigateBack: Bool) { - - // Disable Browser navigation by default - self.state.canNavigateBack = false + private func updateBackButton(canNavigateBack: Bool) { // If the view is not Activation Success, or Welcome page, allow WebView Back Navigation - if self.webViewModel.url?.forComparison() != URL.subscriptionActivateSuccess.forComparison() && - self.webViewModel.url?.forComparison() != URL.subscriptionPurchase.forComparison() { + if !isWelcomePageOrSuccessPage { self.state.canNavigateBack = canNavigateBack self.state.backButtonTitle = UserText.backButtonTitle } else { + self.state.canNavigateBack = false self.state.backButtonTitle = UserText.settingsTitle } @@ -228,9 +252,9 @@ final class SubscriptionEmailViewModel: ObservableObject { } deinit { - cancellables.removeAll() + cleanUp() canGoBackCancellable = nil - + } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift index f2f5feb45d..4890437265 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionExternalLinkViewModel.swift @@ -56,7 +56,11 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { func onFirstAppear() { Task { await setupSubscribers() } webViewModel.navigationCoordinator.navigateTo(url: url) - + } + + private func cleanUp() { + canGoBackCancellable?.cancel() + cancellables.removeAll() } @MainActor @@ -65,7 +69,8 @@ final class SubscriptionExternalLinkViewModel: ObservableObject { } deinit { - cancellables.removeAll() + cleanUp() + canGoBackCancellable = nil } } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 518a9cd0e9..ec8e188e07 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -32,12 +32,12 @@ final class SubscriptionFlowViewModel: ObservableObject { let subFeature: SubscriptionPagesUseSubscriptionFeature let purchaseManager: PurchaseManager var webViewModel: AsyncHeadlessWebViewViewModel - - let viewTitle = UserText.settingsPProSection + var purchaseURL = URL.subscriptionPurchase private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? + private var urlCancellable: AnyCancellable? enum Constants { static let navigationBarHideThreshold = 80.0 @@ -56,6 +56,7 @@ final class SubscriptionFlowViewModel: ObservableObject { var transactionError: SubscriptionPurchaseError? var shouldHideBackButton = false var selectedFeature: SelectedFeature = .none + var viewTitle: String = UserText.subscriptionTitle } // Read only View State - Should only be modified from the VM @@ -217,10 +218,25 @@ final class SubscriptionFlowViewModel: ObservableObject { } } } + + urlCancellable = webViewModel.$url + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let strongSelf = self else { return } + strongSelf.state.canNavigateBack = false + guard let currentURL = self?.webViewModel.url else { return } + if currentURL.forComparison() == URL.addEmailToSubscription.forComparison() || + currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() || + currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() { + strongSelf.state.viewTitle = UserText.subscriptionRestoreAddEmailTitle + } else { + strongSelf.state.viewTitle = UserText.subscriptionTitle + } + } + } private func backButtonForURL(currentURL: URL) -> Bool { - print(currentURL) return currentURL.forComparison() != URL.subscriptionBaseURL.forComparison() && currentURL.forComparison() != URL.subscriptionActivateSuccess.forComparison() && currentURL.forComparison() != URL.subscriptionPurchase.forComparison() @@ -228,6 +244,7 @@ final class SubscriptionFlowViewModel: ObservableObject { private func cleanUp() { canGoBackCancellable?.cancel() + urlCancellable?.cancel() subFeature.cleanup() cancellables.removeAll() } @@ -239,6 +256,8 @@ final class SubscriptionFlowViewModel: ObservableObject { deinit { cleanUp() + canGoBackCancellable = nil + urlCancellable = nil } @MainActor @@ -253,21 +272,21 @@ final class SubscriptionFlowViewModel: ObservableObject { // MARK: - + func onAppear() { + self.state.selectedFeature = .none + } + func onFirstAppear() async { DispatchQueue.main.async { self.resetState() } - await self.setupTransactionObserver() - await self .setupWebViewObservers() - if webViewModel.url == nil { + if webViewModel.url != URL.subscriptionPurchase.forComparison() { self.webViewModel.navigationCoordinator.navigateTo(url: self.purchaseURL) } + await self.setupTransactionObserver() + await self.setupWebViewObservers() Pixel.fire(pixel: .privacyProOfferScreenImpression) } - - func onFirstDisappear() async { - cleanUp() - } @MainActor func restoreAppstoreTransaction() { @@ -298,3 +317,16 @@ final class SubscriptionFlowViewModel: ObservableObject { } #endif + +// TODO: Move to BSK later +private extension URL { + + static var addEmailToSubscriptionSuccess: URL { + subscriptionBaseURL.appendingPathComponent("add-email/success") + } + + static var addEmailToSubscriptionOTP: URL { + subscriptionBaseURL.appendingPathComponent("add-email/otp") + } + +} diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift index 6efa74f56d..4ff55c52a7 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionITPViewModel.swift @@ -139,6 +139,11 @@ final class SubscriptionITPViewModel: ObservableObject { Pixel.fire(pixel: .privacyProIdentityRestorationSettings) } + private func cleanUp() { + canGoBackCancellable?.cancel() + cancellables.removeAll() + } + private func downloadAttachment(from url: URL) async { if let (temporaryURL, _) = try? await URLSession.shared.download(from: url) { let fileManager = FileManager.default @@ -181,7 +186,8 @@ final class SubscriptionITPViewModel: ObservableObject { } deinit { - cancellables.removeAll() + cleanUp() + canGoBackCancellable = nil self.userScript = nil self.subFeature = nil } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 3a56f4cc29..8d1482f65c 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -69,28 +69,30 @@ final class SubscriptionRestoreViewModel: ObservableObject { self.state.isAddingDevice = false } - func onFirstAppear() async { + func onAppear() { DispatchQueue.main.async { self.resetState() } - await setupContent() - await setupTransactionObserver() + Task { await setupContent() } } - - func onFirstDisappear() async { - cleanUp() + + func onFirstAppear() async { + Pixel.fire(pixel: .privacyProSettingsAddDevice) + await setupTransactionObserver() } private func cleanUp() { + subFeature.cleanup() cancellables.removeAll() } + private func setupContent() async { if state.isAddingDevice { DispatchQueue.main.async { self.state.isLoading = true } - Pixel.fire(pixel: .privacyProSettingsAddDevice) + guard let token = accountManager.accessToken else { return } switch await accountManager.fetchAccountDetails(with: token) { case .success(let details): @@ -198,6 +200,10 @@ final class SubscriptionRestoreViewModel: ObservableObject { state.shouldDismissView = true } + deinit { + cleanUp() + } + } #endif diff --git a/DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift b/DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift new file mode 100644 index 0000000000..b6e7a149c3 --- /dev/null +++ b/DuckDuckGo/Subscription/Views/DaxLogoNavbarTitle.swift @@ -0,0 +1,39 @@ +// +// DaxLogoNavbarTitle.swift +// DuckDuckGo +// +// 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 DaxLogoNavbarTitle: View { + + enum Constants { + static let daxLogoSize: CGFloat = 24.0 + static let daxLogo = "Home" + } + + var body: some View { + HStack { + Image(Constants.daxLogo) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) + Text(UserText.subscriptionTitle).daxBodyRegular() + } + } + +} diff --git a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift index 6fc62f7643..ce23b945ea 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionEmailView.swift @@ -60,6 +60,9 @@ struct SubscriptionEmailView: View { ToolbarItemGroup(placement: .navigationBarLeading) { browserBackButton } + ToolbarItemGroup(placement: .principal) { + daxLogoToolbarItem + } } .navigationBarTitleDisplayMode(.inline) .navigationViewStyle(.stack) @@ -126,13 +129,17 @@ struct SubscriptionEmailView: View { } } - .navigationTitle(viewModel.viewTitle) + .navigationTitle(viewModel.state.viewTitle) .onFirstAppear { setUpAppearances() viewModel.onFirstAppear() } + .onAppear { + viewModel.onAppear() + } + } // MARK: - @@ -158,6 +165,13 @@ struct SubscriptionEmailView: View { }) } + @ViewBuilder + private var daxLogoToolbarItem: some View { + if viewModel.state.viewTitle == UserText.subscriptionTitle { + DaxLogoNavbarTitle() + } + } + private func setUpAppearances() { let navAppearance = UINavigationBar.appearance() navAppearance.backgroundColor = UIColor(designSystemColor: .surface) diff --git a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift index 3f8d3fe006..23f206d79f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionFlowView.swift @@ -41,8 +41,6 @@ struct SubscriptionFlowView: View { @State private var isPresentingError: Bool = false enum Constants { - static let daxLogo = "Home" - static let daxLogoSize: CGFloat = 24.0 static let empty = "" static let navButtonPadding: CGFloat = 20.0 static let backButtonImage = "chevron.left" @@ -74,12 +72,10 @@ struct SubscriptionFlowView: View { backButton } ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() + if viewModel.state.viewTitle == UserText.subscriptionTitle { + DaxLogoNavbarTitle() + } else { + Text(viewModel.state.viewTitle).bold() } } } @@ -175,13 +171,9 @@ struct SubscriptionFlowView: View { setUpAppearances() Task { await viewModel.onFirstAppear() } } - - .onFirstDisappear { - Task { await viewModel.onFirstDisappear() } - } - + .onAppear { - Task { await viewModel.onFirstAppear() } + viewModel.onAppear() } .alert(isPresented: $isPresentingError) { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift index 5904854dc2..490d68b1fb 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionITPView.swift @@ -42,8 +42,6 @@ struct SubscriptionITPView: View { @State private var isShowingActivityView = false enum Constants { - static let daxLogo = "Home" - static let daxLogoSize: CGFloat = 24.0 static let empty = "" static let navButtonPadding: CGFloat = 20.0 static let backButtonImage = "chevron.left" @@ -59,13 +57,7 @@ struct SubscriptionITPView: View { backButton } ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() - } + DaxLogoNavbarTitle() } ToolbarItem(placement: .navigationBarTrailing) { shareButton diff --git a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift index 9756c040ca..b6f97b1afe 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionPIRView.swift @@ -33,8 +33,6 @@ struct SubscriptionPIRView: View { @State private var isShowingMacView = false enum Constants { - static let daxLogo = "Home" - static let daxLogoSize: CGFloat = 24.0 static let empty = "" static let navButtonPadding: CGFloat = 20.0 static let lightMask: [Color] = [Color.init(0xFFFFFF, alpha: 0), Color.init(0xFFFFFF, alpha: 0)] @@ -62,13 +60,7 @@ struct SubscriptionPIRView: View { } .toolbar { ToolbarItem(placement: .principal) { - HStack { - Image(Constants.daxLogo) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: Constants.daxLogoSize, height: Constants.daxLogoSize) - Text(viewModel.viewTitle).daxBodyRegular() - } + DaxLogoNavbarTitle() } } .onFirstAppear { diff --git a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift index f010b8aef0..a51a5d6876 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift @@ -151,10 +151,11 @@ struct SubscriptionRestoreView: View { Task { await viewModel.onFirstAppear() } setUpAppearances() } + + .onAppear { + viewModel.onAppear() + } - .onFirstDisappear { - Task { await viewModel.onFirstDisappear() } - } } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift index e93ca2e55f..f3bf14d671 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionSettingsView.swift @@ -38,9 +38,9 @@ struct SubscriptionSettingsView: View { var body: some View { optionsView - .onAppear(perform: { + .onFirstAppear { Pixel.fire(pixel: .privacyProSubscriptionSettings, debounce: 1) - }) + } .navigationBarTitleDisplayMode(.inline) } diff --git a/DuckDuckGo/TabURLInterceptor.swift b/DuckDuckGo/TabURLInterceptor.swift new file mode 100644 index 0000000000..600b18e2fb --- /dev/null +++ b/DuckDuckGo/TabURLInterceptor.swift @@ -0,0 +1,99 @@ +// +// TabURLInterceptor.swift +// DuckDuckGo +// +// 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 Common +import Subscription + +enum InterceptedURL: String { + case privacyPro +} + +struct InterceptedURLInfo { + let id: InterceptedURL + let path: String +} + +protocol TabURLInterceptor { + func allowsNavigatingTo(url: URL) -> Bool +} + +final class TabURLInterceptorDefault: TabURLInterceptor { + + + static let interceptedURLs: [InterceptedURLInfo] = [ + InterceptedURLInfo(id: .privacyPro, path: "/pro") + ] + + func allowsNavigatingTo(url: URL) -> Bool { + + if !url.isPart(ofDomain: "duckduckgo.com") { + return true + } + + guard let components = normalizeScheme(url.absoluteString) else { + return true + } + + guard let matchingURL = urlToIntercept(path: components.path) else { + return true + } + + return Self.handleURLInterception(url: matchingURL.id) + + } +} + +extension TabURLInterceptorDefault { + + private func urlToIntercept(path: String) -> InterceptedURLInfo? { + let results = Self.interceptedURLs.filter { $0.path == path } + return results.first + } + + private func normalizeScheme(_ rawUrl: String) -> URLComponents? { + if !rawUrl.starts(with: URL.URLProtocol.https.scheme) && + !rawUrl.starts(with: URL.URLProtocol.http.scheme) && + rawUrl.contains("://") { + return nil + } + let noScheme = rawUrl.dropping(prefix: URL.URLProtocol.https.scheme).dropping(prefix: URL.URLProtocol.http.scheme) + + return URLComponents(string: "\(URL.URLProtocol.https.scheme)\(noScheme)") + } + + private static func handleURLInterception(url: InterceptedURL) -> Bool { + switch url { + + // Opens the Privacy Pro Subscription Purchase page (if user can purchase) + case .privacyPro: + if SubscriptionPurchaseEnvironment.canPurchase { + NotificationCenter.default.post(name: .urlInterceptPrivacyPro, object: nil) + return false + } + } + return true + + } +} + +extension NSNotification.Name { + static let urlInterceptPrivacyPro: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.urlInterceptPrivacyPro") +} diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 5429c52948..72ab21f196 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -127,6 +127,9 @@ class TabViewController: UIViewController { private var trackersInfoWorkItem: DispatchWorkItem? + private var tabURLInterceptor: TabURLInterceptor = TabURLInterceptorDefault() + private var currentlyLoadedURL: URL? + #if NETWORK_PROTECTION private let netPConnectionObserver = ConnectionStatusObserverThroughSession() private var netPConnectionObserverCancellable: AnyCancellable? @@ -284,6 +287,8 @@ class TabViewController: UIViewController { } private let rulesCompilationMonitor = RulesCompilationMonitor.shared + + private var lastRenderedURL: URL? static func loadFromStoryboard(model: Tab, appSettings: AppSettings = AppDependencyProvider.shared.appSettings, @@ -1092,7 +1097,7 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) { - + let mimeType = MIMEType(from: navigationResponse.response.mimeType) let httpResponse = navigationResponse.response as? HTTPURLResponse @@ -1154,6 +1159,7 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { lastError = nil + lastRenderedURL = webView.url cancelTrackerNetworksAnimation() shouldReloadOnError = false hideErrorMessage() @@ -1165,7 +1171,7 @@ extension TabViewController: WKNavigationDelegate { } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - + self.currentlyLoadedURL = webView.url adClickAttributionDetection.onDidFinishNavigation(url: webView.url) adClickAttributionLogic.onDidFinishNavigation(host: webView.url?.host) hideProgressIndicator() @@ -1394,7 +1400,20 @@ extension TabViewController: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - + + if let url = navigationAction.request.url { + if !tabURLInterceptor.allowsNavigatingTo(url: url) { + decisionHandler(.cancel) + // If there is history or a page loaded keep the tab open + if self.currentlyLoadedURL != nil { + refresh() + } else { + delegate?.tabDidRequestClose(self) + } + return + } + } + if let url = navigationAction.request.url, !url.isDuckDuckGoSearch, true == shouldWaitUntilContentBlockingIsLoaded({ [weak self, webView /* decision handler must be called */] in diff --git a/DuckDuckGoTests/TabURLInterceptorTests.swift b/DuckDuckGoTests/TabURLInterceptorTests.swift new file mode 100644 index 0000000000..b96e78ed9e --- /dev/null +++ b/DuckDuckGoTests/TabURLInterceptorTests.swift @@ -0,0 +1,65 @@ +// +// TabURLInterceptorTests.swift +// DuckDuckGo +// +// 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 XCTest +import Subscription +@testable import DuckDuckGo + +class TabURLInterceptorDefaultTests: XCTestCase { + + var urlInterceptor: TabURLInterceptorDefault! + + override func setUp() { + super.setUp() + // Simulate purchase allowance + SubscriptionPurchaseEnvironment.canPurchase = true + urlInterceptor = TabURLInterceptorDefault() + } + + override func tearDown() { + urlInterceptor = nil + super.tearDown() + } + + func testAllowsNavigationForNonDuckDuckGoDomain() { + let url = URL(string: "https://www.example.com")! + XCTAssertTrue(urlInterceptor.allowsNavigatingTo(url: url)) + } + + func testAllowsNavigationForUninterceptedDuckDuckGoPath() { + let url = URL(string: "https://duckduckgo.com/about")! + XCTAssertTrue(urlInterceptor.allowsNavigatingTo(url: url)) + } + + func testNotificationForInterceptedPrivacyProPath() { + let expectation = self.expectation(forNotification: .urlInterceptPrivacyPro, object: nil, handler: nil) + + let url = URL(string: "https://duckduckgo.com/pro")! + let canNavigate = urlInterceptor.allowsNavigatingTo(url: url) + + // Fail if no note is posted + XCTAssertFalse(canNavigate) + + waitForExpectations(timeout: 1) { error in + if let error = error { + XCTFail("Notification expectation failed: \(error)") + } + } + } +} From cebfbf5e24294846a07e980ca949149c19f56f6f Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Thu, 4 Apr 2024 20:06:47 +0100 Subject: [PATCH 202/245] fix double clearing on autoclear (#2666) Task/Issue URL: https://app.asana.com/0/414709148257752/1206731143449260/f Tech Design URL: CC: Description: Refactoring to inject tabs model into the main view controller Prevents double clearing when using auto-clear + timer Also fixes [Bug: Unable to launch URL properly when auto clear enabled](https://app.asana.com/0/414235014887631/1206454582708636/f) Steps to test this PR: Test standard fire button and fire proofing usage Open some tabs, set cookies (e.g. login to a site), use fire button ensure data is cleared Open some tabs, set cookies (e.g. login to a site), add to fire proofing, ensure data except fireproofed is cleared Open some tabs, set cookies (e.g. login to a site), terminate the app and reload, ensure tab loads as expected Autoclear with Terminate Update settings to enable auto clear on terminate Open some tabs, set cookies (e.g. login to a site) Terminate the app Launch the app, ensure tabs and data are cleared Autoclear with Terminate and Delay Update settings to enable auto clear on terminate with a delay Hack the code to return true for the selected delay in the shouldClearData function Background the app Use another app (e.g news) to launch a URL in DDG (either through default browser or by share extension) Ensure tabs and data are cleared Autoclear with tabs only Repeat above tests but with tabs only selected in auto clear settings Ensure tabs are closed but data is retained --- DuckDuckGo/AppDelegate.swift | 33 ++++++++++- DuckDuckGo/AutoClear.swift | 9 +-- DuckDuckGo/MainViewController.swift | 84 +++++++++++++--------------- DuckDuckGo/TabManager.swift | 5 +- DuckDuckGoTests/AutoClearTests.swift | 17 ++++-- 5 files changed, 87 insertions(+), 61 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index fa5cdf9eea..f9cda9df50 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -266,7 +266,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { }) } + let previewsSource = TabPreviewsSource() let historyManager = makeHistoryManager() + let tabsModel = prepareTabsModel(previewsSource: previewsSource) #if APP_TRACKING_PROTECTION let main = MainViewController(bookmarksDatabase: bookmarksDatabase, @@ -275,14 +277,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate { historyManager: historyManager, syncService: syncService, syncDataProviders: syncDataProviders, - appSettings: AppDependencyProvider.shared.appSettings) + appSettings: AppDependencyProvider.shared.appSettings, + previewsSource: previewsSource, + tabsModel: tabsModel) #else let main = MainViewController(bookmarksDatabase: bookmarksDatabase, bookmarksDatabaseCleaner: syncDataProviders.bookmarksAdapter.databaseCleaner, historyManager: historyManager, syncService: syncService, syncDataProviders: syncDataProviders, - appSettings: AppDependencyProvider.shared.appSettings) + appSettings: AppDependencyProvider.shared.appSettings, + previewsSource: previewsSource, + tabsModel: tabsModel) #endif main.loadViewIfNeeded() @@ -345,6 +351,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return true } + private func prepareTabsModel(previewsSource: TabPreviewsSource = TabPreviewsSource(), + appSettings: AppSettings = AppDependencyProvider.shared.appSettings, + isDesktop: Bool = UIDevice.current.userInterfaceIdiom == .pad) -> TabsModel { + let isPadDevice = UIDevice.current.userInterfaceIdiom == .pad + let tabsModel: TabsModel + if AutoClearSettingsModel(settings: appSettings) != nil { + tabsModel = TabsModel(desktop: isPadDevice) + tabsModel.save() + previewsSource.removeAllPreviews() + } else { + if let storedModel = TabsModel.get() { + // Save new model in case of migration + storedModel.save() + tabsModel = storedModel + } else { + tabsModel = TabsModel(desktop: isPadDevice) + } + } + return tabsModel + } + private func makeHistoryManager() -> HistoryManager { let historyManager = HistoryManager(privacyConfigManager: ContentBlocking.shared.privacyConfigurationManager, variantManager: DefaultVariantManager(), @@ -733,7 +760,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } Task { @MainActor in - await autoClear?.applicationWillMoveToForeground() + // Autoclear should have happened by now showKeyboardIfSettingOn = false if !handleAppDeepLink(app, mainViewController, url) { diff --git a/DuckDuckGo/AutoClear.swift b/DuckDuckGo/AutoClear.swift index eca57dce95..bda1c46614 100644 --- a/DuckDuckGo/AutoClear.swift +++ b/DuckDuckGo/AutoClear.swift @@ -33,14 +33,15 @@ class AutoClear { private let worker: AutoClearWorker private var timestamp: TimeInterval? - private lazy var appSettings = AppDependencyProvider.shared.appSettings - + private let appSettings: AppSettings + var isClearingEnabled: Bool { return AutoClearSettingsModel(settings: appSettings) != nil } - init(worker: AutoClearWorker) { + init(worker: AutoClearWorker, appSettings: AppSettings = AppDependencyProvider.shared.appSettings) { self.worker = worker + self.appSettings = appSettings } @MainActor @@ -86,8 +87,8 @@ class AutoClear { let timestamp = timestamp, shouldClearData(elapsedTime: Date().timeIntervalSince1970 - timestamp) else { return } + self.timestamp = nil worker.clearNavigationStack() await clearData() - self.timestamp = nil } } diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index e144e49c99..305badad6b 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -84,11 +84,13 @@ class MainViewController: UIViewController { var tabsBarController: TabsBarViewController? var suggestionTrayController: SuggestionTrayViewController? - var tabManager: TabManager! - let previewsSource = TabPreviewsSource() + let tabManager: TabManager + let previewsSource: TabPreviewsSource let appSettings: AppSettings private var launchTabObserver: LaunchTabNotification.Observer? + var doRefreshAfterClear = true + #if APP_TRACKING_PROTECTION private let appTrackingProtectionDatabase: CoreDataDatabase #endif @@ -139,7 +141,7 @@ class MainViewController: UIViewController { lazy var tabSwitcherTransition = TabSwitcherTransitionDelegate() var currentTab: TabViewController? { - return tabManager?.current(createIfNeeded: false) + return tabManager.current(createIfNeeded: false) } var searchBarRect: CGRect { @@ -174,7 +176,9 @@ class MainViewController: UIViewController { historyManager: HistoryManager, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, - appSettings: AppSettings = AppUserDefaults() + appSettings: AppSettings = AppUserDefaults(), + previewsSource: TabPreviewsSource, + tabsModel: TabsModel ) { self.appTrackingProtectionDatabase = appTrackingProtectionDatabase self.bookmarksDatabase = bookmarksDatabase @@ -185,9 +189,17 @@ class MainViewController: UIViewController { self.favoritesViewModel = FavoritesListViewModel(bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: appSettings.favoritesDisplayMode) self.bookmarksCachingSearch = BookmarksCachingSearch(bookmarksStore: CoreDataBookmarksSearchStore(bookmarksStore: bookmarksDatabase)) self.appSettings = appSettings - + self.previewsSource = previewsSource + + self.tabManager = TabManager(model: tabsModel, + previewsSource: previewsSource, + bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, + syncService: syncService) + super.init(nibName: nil, bundle: nil) - + + tabManager.delegate = self bindFavoritesDisplayMode() bindSyncService() } @@ -198,7 +210,9 @@ class MainViewController: UIViewController { historyManager: HistoryManager, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, - appSettings: AppSettings + appSettings: AppSettings, + previewsSource: TabPreviewsSource, + tabsModel: TabsModel ) { self.bookmarksDatabase = bookmarksDatabase self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner @@ -208,9 +222,18 @@ class MainViewController: UIViewController { self.favoritesViewModel = FavoritesListViewModel(bookmarksDatabase: bookmarksDatabase, favoritesDisplayMode: appSettings.favoritesDisplayMode) self.bookmarksCachingSearch = BookmarksCachingSearch(bookmarksStore: CoreDataBookmarksSearchStore(bookmarksStore: bookmarksDatabase)) self.appSettings = appSettings - + self.previewsSource = previewsSource + + self.tabManager = TabManager(model: tabsModel, + previewsSource: previewsSource, + bookmarksDatabase: bookmarksDatabase, + historyManager: historyManager, + syncService: syncService) + + super.init(nibName: nil, bundle: nil) + tabManager.delegate = self bindSyncService() } #endif @@ -264,7 +287,6 @@ class MainViewController: UIViewController { initTabButton() initMenuButton() initBookmarksButton() - configureTabManager() loadInitialView() previewsSource.prepare() addLaunchTabNotificationObserver() @@ -699,40 +721,6 @@ class MainViewController: UIViewController { dismissOmniBar() } - private func configureTabManager() { - - let isPadDevice = UIDevice.current.userInterfaceIdiom == .pad - - let tabsModel: TabsModel - if let settings = AutoClearSettingsModel(settings: appSettings) { - // This needs to be refactored so that tabs model is injected and cleared before view did load, - // but for now, ensure this happens in the right order by clearing data here too, if needed. - tabsModel = TabsModel(desktop: isPadDevice) - tabsModel.save() - previewsSource.removeAllPreviews() - - if settings.action.contains(.clearData) { - Task { @MainActor in - await forgetData() - } - } - } else { - if let storedModel = TabsModel.get() { - // Save new model in case of migration - storedModel.save() - tabsModel = storedModel - } else { - tabsModel = TabsModel(desktop: isPadDevice) - } - } - tabManager = TabManager(model: tabsModel, - previewsSource: previewsSource, - bookmarksDatabase: bookmarksDatabase, - historyManager: historyManager, - syncService: syncService, - delegate: self) - } - private func addLaunchTabNotificationObserver() { launchTabObserver = LaunchTabNotification.addObserver(handler: { [weak self] urlString in guard let self = self else { return } @@ -890,6 +878,7 @@ class MainViewController: UIViewController { selectTab(existing) return } else if reuseExisting, let existing = tabManager.firstHomeTab() { + doRefreshAfterClear = false tabManager.selectTab(existing) loadUrl(url, fromExternalLink: fromExternalLink) } else { @@ -2020,7 +2009,7 @@ extension MainViewController: TabDelegate { if currentTab == tab { refreshControls() } - tabManager?.save() + tabManager.save() tabsBarController?.refresh(tabsModel: tabManager.model) // note: model in swipeTabsCoordinator doesn't need to be updated here // https://app.asana.com/0/414235014887631/1206847376910045/f @@ -2267,7 +2256,7 @@ extension MainViewController: TabSwitcherButtonDelegate { } func showTabSwitcher() { - guard let currentTab = currentTab ?? tabManager?.current(createIfNeeded: true) else { + guard let currentTab = currentTab ?? tabManager.current(createIfNeeded: true) else { fatalError("Unable to get current tab") } @@ -2324,6 +2313,10 @@ extension MainViewController: AutoClearWorker { } func refreshUIAfterClear() { + guard doRefreshAfterClear else { + doRefreshAfterClear = true + return + } showBars() attachHomeScreen() tabsBarController?.refresh(tabsModel: tabManager.model) @@ -2336,6 +2329,7 @@ extension MainViewController: AutoClearWorker { refreshUIAfterClear() } + @MainActor func forgetData() async { guard !clearInProgress else { assertionFailure("Shouldn't get called multiple times") diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index a6bf166cf2..63cc6b3f48 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -35,6 +35,7 @@ class TabManager { private let historyManager: HistoryManager private let syncService: DDGSyncing private var previewsSource: TabPreviewsSource + weak var delegate: TabDelegate? @UserDefaultsWrapper(key: .faviconTabsCacheNeedsCleanup, defaultValue: true) @@ -45,14 +46,12 @@ class TabManager { previewsSource: TabPreviewsSource, bookmarksDatabase: CoreDataDatabase, historyManager: HistoryManager, - syncService: DDGSyncing, - delegate: TabDelegate) { + syncService: DDGSyncing) { self.model = model self.previewsSource = previewsSource self.bookmarksDatabase = bookmarksDatabase self.historyManager = historyManager self.syncService = syncService - self.delegate = delegate let index = model.currentIndex let tab = model.tabs[index] if tab.link != nil { diff --git a/DuckDuckGoTests/AutoClearTests.swift b/DuckDuckGoTests/AutoClearTests.swift index 2edb1e52ee..45e66212ba 100644 --- a/DuckDuckGoTests/AutoClearTests.swift +++ b/DuckDuckGoTests/AutoClearTests.swift @@ -49,19 +49,20 @@ class AutoClearTests: XCTestCase { private var worker: MockWorker! private var logic: AutoClear! + private var appSettings: AppSettingsMock! + + override func setUp() async throws { + try await super.setUp() - override func setUp() { - super.setUp() - worker = MockWorker() - logic = AutoClear(worker: worker) + appSettings = AppSettingsMock() + logic = AutoClear(worker: worker, appSettings: appSettings) } // Note: applicationDidLaunch based clearing has moved to "configureTabManager" function of // MainViewController to ensure that tabs are removed before the data is cleared. func testWhenTimingIsSetToTerminationThenOnlyRestartClearsData() async { - let appSettings = AppUserDefaults() appSettings.autoClearAction = .clearData appSettings.autoClearTiming = .termination @@ -70,10 +71,14 @@ class AutoClearTests: XCTestCase { XCTAssertEqual(worker.clearNavigationStackInvocationCount, 0) XCTAssertEqual(worker.forgetDataInvocationCount, 0) + + await logic.applicationWillMoveToForeground() + + XCTAssertEqual(worker.clearNavigationStackInvocationCount, 0) + XCTAssertEqual(worker.forgetDataInvocationCount, 0) } func testWhenDesiredTimingIsSetThenDataIsClearedOnceTimeHasElapsed() async { - let appSettings = AppUserDefaults() appSettings.autoClearAction = .clearData let cases: [AutoClearSettingsModel.Timing: TimeInterval] = [.delay5min: 5 * 60, From 96954a48089e90aabaf4a8fcd15663a6df04ed23 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Thu, 4 Apr 2024 16:09:38 -0400 Subject: [PATCH 203/245] Enforce subscription check (#2677) Co-authored-by: Sam Symons --- DuckDuckGo/AppDelegate.swift | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index d6a15bc380..6e933475f0 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -515,18 +515,30 @@ class AppDelegate: UIResponder, UIApplicationDelegate { presentVPNEarlyAccessOverAlert() Task { - let controller = NetworkProtectionTunnelController() - - if await controller.isConnected { - DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ - "reason": "thank-you-dialog" - ]) - } - - await controller.stop() - await controller.removeVPN() + await self.stopAndRemoveVPN(with: "thank-you-dialog") } + } else if vpnFeatureVisibility.isPrivacyProLaunched() && !AccountManager().isUserAuthenticated { + Task { + await self.stopAndRemoveVPN(with: "subscription-check") + } + } + } + + private func stopAndRemoveVPN(with reason: String) async { + let controller = NetworkProtectionTunnelController() + guard await controller.isInstalled else { + return } + + let isConnected = await controller.isConnected + + DailyPixel.fireDailyAndCount(pixel: .privacyProVPNBetaStoppedWhenPrivacyProEnabled, withAdditionalParameters: [ + "reason": reason, + "vpn-connected": String(isConnected) + ]) + + await controller.stop() + await controller.removeVPN() } func updateSubscriptionStatus() { From 913d725365138c4af2c21e8a9c6020ec1b56fa25 Mon Sep 17 00:00:00 2001 From: Michal Smaga Date: Thu, 4 Apr 2024 22:54:05 +0200 Subject: [PATCH 204/245] Release 7.114.0-1 (#2679) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index f537ec6d44..4f16f1dc8c 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8282,7 +8282,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8319,7 +8319,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8411,7 +8411,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8439,7 +8439,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8589,7 +8589,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8615,7 +8615,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8680,7 +8680,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8715,7 +8715,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8749,7 +8749,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8780,7 +8780,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9067,7 +9067,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9098,7 +9098,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9127,7 +9127,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9161,7 +9161,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9192,7 +9192,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9225,11 +9225,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9463,7 +9463,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9490,7 +9490,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9523,7 +9523,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9561,7 +9561,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9597,7 +9597,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9632,11 +9632,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9810,11 +9810,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9843,10 +9843,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From a2e664689665b056bea1c51c970cfe50d3e6ffaf Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 5 Apr 2024 00:20:10 +0100 Subject: [PATCH 205/245] Fix missing tab delegate on launch (#2680) Task/Issue URL: https://app.asana.com/0/414235014887631/1207004361509188/f Tech Design URL: CC: Description: Fixes a bug in recent PR which means the settings menu doesn't work when launching the app with a tab selected. Steps to test this PR: Open a tab and browse to a site Terminate the app Launch the app - page should load Access browsing menu, select settings - settings should load Open some more tabs Close some tabs Use the tab switcher a bit --- DuckDuckGo/TabManager.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 63cc6b3f48..95986bc340 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -52,12 +52,6 @@ class TabManager { self.bookmarksDatabase = bookmarksDatabase self.historyManager = historyManager self.syncService = syncService - let index = model.currentIndex - let tab = model.tabs[index] - if tab.link != nil { - let controller = buildController(forTab: tab, inheritedAttribution: nil) - tabControllerCache.append(controller) - } registerForNotifications() } From 8d4fbdaa67b0b063b2173ade294c0de7ae6a8a0b Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 5 Apr 2024 09:18:24 +0100 Subject: [PATCH 206/245] Fix history debug view controller causing crash on tap (#2681) Task/Issue URL: https://app.asana.com/0/414709148257752/1207004361509202/f Tech Design URL: CC: Description: Fixes crash when tapping on history debug view controller. Steps to test this PR: Access the history debug option from Settings > Debug > History Should not crash --- DuckDuckGo/Debug.storyboard | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index 95327eadf4..a3bf02c05e 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -248,7 +248,7 @@ - + @@ -319,6 +319,22 @@ + + + + + + + + + + + + + + + + @@ -873,34 +889,34 @@ - + - + - + - + @@ -941,22 +957,6 @@ - - - - - - - - - - - - - - - - @@ -968,13 +968,13 @@ - + - + From 3dfacc64a0980341ccfeb650fe6a31f1dd3a0df9 Mon Sep 17 00:00:00 2001 From: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Date: Fri, 5 Apr 2024 13:16:05 +0200 Subject: [PATCH 207/245] Update autoconsent to v10.5.0 (#2671) Co-authored-by: muodov --- DuckDuckGo/Autoconsent/autoconsent-bundle.js | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index 68d3e39583..a991e9926d 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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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 g(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 y="#truste-show-consent",w="#truste-consent-track",C=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${w}`],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(`${y},${w}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${w}`,"all")}openFrame(){this.click(y)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${w}`),this.click(y),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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 b((()=>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 d{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 d{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")||(k(h(),"#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 d{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 b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}],v=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var f=[{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:[]}],A={"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"}]}},E={autoconsent:f,consentomatic:A},x=Object.freeze({__proto__:null,autoconsent:f,consentomatic:A,default:E});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 v(this)}initialize(e,t){const o=g(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(){C.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,x);window.autoconsentMessageCallback=e=>{O.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,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(i)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,a);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,a);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,a);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,a);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 n(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}}var i=0;function n(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function a(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var s=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)}},r={pending:new Map,sendContentMessage:null};var l={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||(e.checked="moove_gdpr_strict_cookies"===e.name||"moove_gdpr_strict_cookies"===e.id)}))||!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 p={main:!0,frame:!1,urlPattern:""},d=class{constructor(e){this.runContext=p,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=l[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),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new s(o);return r.pending.set(c.id,c),c.promise}(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...p,...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)}},u=class extends d{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||p}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}},m=class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=p,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}};function h(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 k(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 b(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(b(e,t-1,o))}),o)})):Promise.resolve(c)}function _(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 g(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 y="#truste-show-consent",w="#truste-consent-track",C=[class extends d{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${w}`],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(`${y},${w}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${w}`,"all")}openFrame(){this.click(y)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(k(h(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${w}`),this.click(y),setTimeout((()=>{h().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 d{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 b((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 b((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await b((()=>"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 d{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 d{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:!0,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 b((()=>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 d{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 d{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")||(k(h(),"#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 d{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 b((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends d{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 d{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 d{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 b((()=>!!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 d{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 _(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 d{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 b((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}},class extends d{constructor(){super(...arguments),this.name="tumblr-com",this.runContext={urlPattern:"^https://(www\\.)?tumblr\\.com/"}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}get prehideSelectors(){return["#cmp-app-container"]}async detectCmp(){return this.elementExists("#cmp-app-container")}async detectPopup(){return this.elementVisible("#cmp-app-container","any")}async optOut(){let e=document.querySelector("#cmp-app-container iframe"),t=e.contentDocument?.querySelector(".cmp-components-button.is-secondary");return!!t&&(t.click(),await b((()=>{const e=document.querySelector("#cmp-app-container iframe");return!!e.contentDocument?.querySelector(".cmp__dialog input")}),5,500),e=document.querySelector("#cmp-app-container iframe"),t=e.contentDocument?.querySelector(".cmp-components-button.is-secondary"),!!t&&(t.click(),!0))}async optIn(){const e=document.querySelector("#cmp-app-container iframe").contentDocument.querySelector(".cmp-components-button.is-primary");return!!e&&(e.click(),!0)}}],v=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]=_(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),b((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return b((()=>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 k(h(),e,t)}prehide(e){const t=h("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),k(t,e,"opacity")}undoPrehide(){const e=h("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)}};var f=[{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="cookie-policy"]'},{waitFor:'div[role="dialog"] button[role=switch]'},{click:'div[role="dialog"] button:nth-child(2):not([role])'},{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 opt-out",prehideSelectors:['[aria-describedby="cookieconsent:desc"].cc-type-opt-out'],detectCmp:[{exists:'[aria-describedby="cookieconsent:desc"].cc-type-opt-out'}],detectPopup:[{visible:'[aria-describedby="cookieconsent:desc"].cc-type-opt-out'}],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 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:[{if:{exists:"#cookiescript_reject"},then:[{wait:100},{click:"#cookiescript_reject"}],else:[{click:"#cookiescript_manage"},{waitForVisible:".cookiescript_fsd_main"},{waitForThenClick:"#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:"Ensighten ensModal",prehideSelectors:[".ensModal"],detectCmp:[{exists:".ensModal"}],detectPopup:[{visible:".ensModal"}],optIn:[{waitForThenClick:"#modalAcceptButton"}],optOut:[{waitForThenClick:".ensCheckbox:checked",all:!0},{waitForThenClick:"#ensSave"}]},{name:"Ensighten ensNotifyBanner",prehideSelectors:["#ensNotifyBanner"],detectCmp:[{exists:"#ensNotifyBanner"}],detectPopup:[{visible:"#ensNotifyBanner"}],optIn:[{waitForThenClick:"#ensCloseBanner"}],optOut:[{waitForThenClick:"#ensRejectAll,#rejectAll,#ensRejectBanner"}]},{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:not(.moove-gdpr-info-bar-hidden)"}],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",runContext:{urlPattern:"^https://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)"},{if:{exists:"div.b-cookies-informer__switchers"},then:[{click:"div.b-cookies-informer__switchers input:not([disabled])",all:!0},{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:[".consent__wrapper"],runContext:{urlPattern:"^https://www\\.strato\\.de/"},detectCmp:[{exists:".consent"}],detectPopup:[{visible:".consent"}],optIn:[{click:"button.consentAgree"}],optOut:[{click:"button.consentSettings"},{waitForThenClick:"button#consentSubmit"}]},{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:"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:[]}],A={"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"}]}},E={autoconsent:f,consentomatic:A},x=Object.freeze({__proto__:null,autoconsent:f,consentomatic:A,default:E});const O=new class{constructor(e,t=null,o=null){if(this.id=a(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},r.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 v(this)}initialize(e,t){const o=g(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(){C.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 u(e,this))}addConsentomaticCMP(e,t){this.rules.push(new m(`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.filter((e=>e.prehideSelectors&&e.checkRunContext())).reduce(((e,t)=>[...e,...t.prehideSelectors]),["#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=r.pending.get(e);o?(r.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=>{O.receiveMessageCallback(e)}))}),null,x);window.autoconsentMessageCallback=e=>{O.receiveMessageCallback(e)}}(); diff --git a/package-lock.json b/package-lock.json index 6688f65c51..7ab7e774b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "ios", "version": "1.0.0", "dependencies": { - "@duckduckgo/autoconsent": "^10.3.0" + "@duckduckgo/autoconsent": "^10.5.0" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", @@ -135,9 +135,9 @@ } }, "node_modules/@duckduckgo/autoconsent": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.3.0.tgz", - "integrity": "sha512-dUf37qkaYDuXEytU9mNNLGw28S1t1M1dFnvMHZDV9BpINVJeAl1ye7CmlABuGlDs6URrp2ZLZ5IxcKQhQglYcw==" + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.5.0.tgz", + "integrity": "sha512-4mdp9mwBiE+IKTvN84iRA8d7eSkJ5xMaQvhvbgw7XlD1VOJlfiJPhP8PJWV+wyc7DNVHMtcdUXiD+ICw/SJBRA==" }, "node_modules/@eslint/eslintrc": { "version": "0.4.3", diff --git a/package.json b/package.json index 30bc669090..ff4c3a5667 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,6 @@ "rollup-plugin-terser": "^7.0.2" }, "dependencies": { - "@duckduckgo/autoconsent": "^10.3.0" + "@duckduckgo/autoconsent": "^10.5.0" } } From c7fefb81d266413cd9daa0535fe521fbf452e007 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 5 Apr 2024 14:10:24 +0100 Subject: [PATCH 208/245] scroll to selected item after opening new tab, etc (#2689) Task/Issue URL: https://app.asana.com/0/414235014887631/1207005077017362/f Tech Design URL: CC: Description: Make sure tabs collection is in sync with selected tab after opening. Steps to test this PR: Open a web page Long press a link and open in new tab Ensure omni bar works as expected Swipe between tabs and check omni bar Use tab switcher and check omni bar --- DuckDuckGo/MainViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 305badad6b..05681b9004 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -888,7 +888,7 @@ class MainViewController: UIViewController { refreshTabIcon() refreshControls() tabsBarController?.refresh(tabsModel: tabManager.model) - swipeTabsCoordinator?.refresh(tabsModel: tabManager.model) + swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) } if clearInProgress { From e1105b7ed2354c508f5b9164abe73c0817b2ddd5 Mon Sep 17 00:00:00 2001 From: bwaresiak Date: Fri, 5 Apr 2024 18:39:35 +0200 Subject: [PATCH 209/245] Expand maestro tests (#2691) Task/Issue URL: https://app.asana.com/0/0/1207007045746915/f https://app.asana.com/0/856498667320406/1207007045746915 --- .maestro/browser_features/opening_tabs.yaml | 65 +++++++++++++++++++ .../data_clearing_tests/01_fire_proofing.yml | 2 +- .maestro/shared/check_number_of_tabs.yaml | 7 ++ 3 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 .maestro/browser_features/opening_tabs.yaml create mode 100644 .maestro/shared/check_number_of_tabs.yaml diff --git a/.maestro/browser_features/opening_tabs.yaml b/.maestro/browser_features/opening_tabs.yaml new file mode 100644 index 0000000000..8b2aa60e1a --- /dev/null +++ b/.maestro/browser_features/opening_tabs.yaml @@ -0,0 +1,65 @@ +# tabs.yaml +appId: com.duckduckgo.mobile.ios +tags: + - release + +--- + +# Set up +- clearState +- launchApp +- runFlow: + when: + visible: + text: "Let’s Do It!" + index: 0 + file: ../shared/onboarding.yaml + +# Load Site +- assertVisible: + id: "searchEntry" +- tapOn: + id: "searchEntry" +- inputText: "https://privacy-test-pages.site" +- pressKey: Enter + +# Manage onboarding +- runFlow: + when: + visible: + text: "Got It" + index: 0 + file: ../shared/onboarding_browsing.yaml + +- assertVisible: ".*Privacy Test Pages.*" +- tapOn: "Links Open in New Window" + +# Validate there's one tab +- runFlow: + file: ../shared/check_number_of_tabs.yaml + env: + TITLE: "1 Private Tab" + +- tapOn: "Opens in new window" +- runFlow: + file: ../shared/check_number_of_tabs.yaml + env: + TITLE: "2 Private Tabs" + +- tapOn: "Close" +- assertVisible: "A link that opens in a new window" +- tapOn: "Opens in new window" +- runFlow: + file: ../shared/check_number_of_tabs.yaml + env: + TITLE: "2 Private Tabs" + +- tapOn: "Browse Back" +- assertVisible: "A link that opens in a new window" + +# Workaround - for some reason Tab Switcher button is not found by maestro at this point. +- tapOn: "Refresh Page" +- runFlow: + file: ../shared/check_number_of_tabs.yaml + env: + TITLE: "1 Private Tab" diff --git a/.maestro/data_clearing_tests/01_fire_proofing.yml b/.maestro/data_clearing_tests/01_fire_proofing.yml index 706fabe76e..9dd1d4b5a3 100644 --- a/.maestro/data_clearing_tests/01_fire_proofing.yml +++ b/.maestro/data_clearing_tests/01_fire_proofing.yml @@ -1,6 +1,6 @@ appId: com.duckduckgo.mobile.ios tags: - - dataclearing + - privacy --- diff --git a/.maestro/shared/check_number_of_tabs.yaml b/.maestro/shared/check_number_of_tabs.yaml new file mode 100644 index 0000000000..1ee4302570 --- /dev/null +++ b/.maestro/shared/check_number_of_tabs.yaml @@ -0,0 +1,7 @@ +appId: com.duckduckgo.mobile.ios +--- + +- assertVisible: "Tab Switcher" +- tapOn: "Tab Switcher" +- assertVisible: ${TITLE} +- tapOn: "Done" \ No newline at end of file From 1504f88c5651345433ba5260d97c7940145d3842 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Fri, 5 Apr 2024 22:09:17 +0200 Subject: [PATCH 210/245] Use trait collection to control theme changes (#2672) --- Core/UIViewControllerExtension.swift | 11 --- DuckDuckGo.xcodeproj/project.pbxproj | 16 +--- .../AddOrEditBookmarkViewController.swift | 13 +-- DuckDuckGo/AppIconSettingsCell.swift | 15 +++- .../AppIconSettingsViewController.swift | 8 +- .../ErrorInfoDark.imageset/Contents.json | 12 --- .../ErrorInfoLight.imageset/Contents.json | 15 ---- .../ErrorInfoUniversal.imageset/Contents.json | 25 ++++++ .../errorImgDark.pdf | Bin .../errorImgLight.pdf | Bin .../LogoDarkText.imageset/Contents.json | 17 ---- .../LogoLightText.imageset/Contents.json | 17 ---- .../LogoText.imageset/Contents.json | 38 +++++++++ .../dark_portrait_logo.pdf | Bin .../light_portrait_logo.pdf | Bin .../updatedLogoGrayText2.pdf | Bin .../updatedLogoWhiteText2.pdf | Bin .../Contents.json | 15 ---- .../Contents.json | 10 +++ .../tabsToggleGrid-Dark.pdf | Bin .../tabsToggleGrid-Light.pdf | Bin .../Contents.json | 15 ---- .../Contents.json | 10 +++ .../tabsToggleList-Dark.pdf | Bin .../tabsToggleList-Light.pdf | Bin DuckDuckGo/AuthenticationViewController.swift | 14 +--- .../AutoClearSettingsViewController.swift | 9 +- DuckDuckGo/AutocompleteViewController.swift | 13 +-- .../AutoconsentSettingsViewController.swift | 14 ++-- DuckDuckGo/AutofillEmptySearchView.swift | 6 +- DuckDuckGo/AutofillItemsEmptyView.swift | 6 +- .../AutofillLoginDetailsViewController.swift | 9 +- ...ofillLoginSettingsListViewController.swift | 10 +-- DuckDuckGo/AutofillNoAuthAvailableView.swift | 6 +- .../Base.lproj/Authentication.storyboard | 20 ++--- DuckDuckGo/Base.lproj/Bookmarks.storyboard | 12 +-- DuckDuckGo/Base.lproj/Feedback.storyboard | 14 ++-- DuckDuckGo/Base.lproj/Tab.storyboard | 34 +++----- DuckDuckGo/Base.lproj/TabSwitcher.storyboard | 26 +++--- DuckDuckGo/BasicAuthenticationAlert.swift | 1 - DuckDuckGo/BlankSnapshotViewController.swift | 16 ++-- DuckDuckGo/BookmarksViewController.swift | 8 +- .../BrowsingMenuEntryViewCell.swift | 7 +- .../BrowsingMenuViewController.swift | 36 ++++---- .../ConfigurationURLDebugViewController.swift | 2 +- DuckDuckGo/CriticalAlerts.swift | 5 -- DuckDuckGo/DarkTheme.swift | 65 --------------- DuckDuckGo/DaxDialogViewController.swift | 32 ++++---- DuckDuckGo/DaxOnboardingViewController.swift | 1 - DuckDuckGo/DefaultTheme.swift | 77 ++++++++++++++++++ .../DoNotSellSettingsViewController.swift | 7 +- .../DownloadsListHostingController.swift | 1 - DuckDuckGo/EmailSignupViewController.swift | 7 +- DuckDuckGo/FavoriteHomeCell.swift | 21 +++-- DuckDuckGo/FavoritesOverlay.swift | 23 +++--- DuckDuckGo/FavoritesViewController.swift | 18 ++-- DuckDuckGo/FeedbackFormViewController.swift | 7 +- DuckDuckGo/FeedbackPickerViewController.swift | 7 +- DuckDuckGo/FeedbackViewController.swift | 10 +-- DuckDuckGo/FindInPageView.swift | 7 +- DuckDuckGo/ForgetDataAlert.swift | 1 - DuckDuckGo/GestureToolbarButton.swift | 7 +- DuckDuckGo/HomeCollectionView.swift | 16 +--- DuckDuckGo/HomeViewController.swift | 14 ++-- DuckDuckGo/HomeViewSectionRenderers.swift | 28 ++----- .../KeyboardSettingsViewController.swift | 7 +- DuckDuckGo/LightTheme.swift | 65 --------------- DuckDuckGo/MainViewController+Segues.swift | 6 +- DuckDuckGo/MainViewController.swift | 58 +++++-------- DuckDuckGo/MainViewCoordinator.swift | 6 +- DuckDuckGo/MenuButton.swift | 30 +++++-- .../NetworkProtectionRootViewController.swift | 7 +- DuckDuckGo/NoMicPermissionAlert.swift | 1 - DuckDuckGo/OmniBar.swift | 16 +++- .../OmniBarNotificationContainerView.swift | 8 +- .../PositiveFeedbackViewController.swift | 15 +++- ...PreserveLoginsSettingsViewController.swift | 9 +- .../PrivacyDashboardViewController.swift | 43 +++++++--- DuckDuckGo/PrivacyIconView.swift | 30 +++++-- DuckDuckGo/PrivacyInfoContainerView.swift | 36 ++++---- DuckDuckGo/ProgressView.swift | 15 +++- DuckDuckGo/SaveLoginViewController.swift | 1 - DuckDuckGo/SaveToDownloadsAlert.swift | 1 - DuckDuckGo/SettingsHostingController.swift | 7 +- DuckDuckGo/SettingsViewModel.swift | 2 - DuckDuckGo/SuggestionTrayViewController.swift | 5 +- DuckDuckGo/SwipeTabsCoordinator.swift | 1 - .../SyncSettingsViewController+Themable.swift | 41 ---------- DuckDuckGo/SyncSettingsViewController.swift | 25 +++++- DuckDuckGo/TabManager.swift | 9 -- DuckDuckGo/TabSwitcherButton.swift | 34 +++++--- DuckDuckGo/TabSwitcherViewController.swift | 30 +++---- DuckDuckGo/TabViewCell.swift | 4 +- DuckDuckGo/TabViewController.swift | 16 +--- ...ViewControllerLongPressMenuExtension.swift | 1 - DuckDuckGo/TabViewGridCell.swift | 59 ++++++++------ DuckDuckGo/TabViewListCell.swift | 16 +++- DuckDuckGo/TabsBarViewController.swift | 8 +- .../TextSizeSettingsViewController.swift | 9 +- DuckDuckGo/Themable.swift | 55 +------------ DuckDuckGo/ThemableNavigationController.swift | 8 -- DuckDuckGo/Theme.swift | 1 - DuckDuckGo/ThemeManager.swift | 66 ++------------- DuckDuckGo/TrackerImageCache.swift | 6 +- DuckDuckGo/UIApplicationExtension.swift | 6 ++ .../UnprotectedSitesViewController.swift | 19 ++--- DuckDuckGo/VPNWaitlistViewController.swift | 1 - .../WebContainerNavigationController.swift | 2 +- DuckDuckGo/WebContainerViewController.swift | 4 - DuckDuckGoTests/ThemeManagerTests.swift | 68 +++++++++++----- 110 files changed, 762 insertions(+), 949 deletions(-) delete mode 100644 DuckDuckGo/Assets.xcassets/ErrorInfoDark.imageset/Contents.json delete mode 100644 DuckDuckGo/Assets.xcassets/ErrorInfoLight.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/Contents.json rename DuckDuckGo/Assets.xcassets/{ErrorInfoDark.imageset => ErrorInfoUniversal.imageset}/errorImgDark.pdf (100%) rename DuckDuckGo/Assets.xcassets/{ErrorInfoLight.imageset => ErrorInfoUniversal.imageset}/errorImgLight.pdf (100%) delete mode 100644 DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/Contents.json delete mode 100644 DuckDuckGo/Assets.xcassets/LogoLightText.imageset/Contents.json create mode 100644 DuckDuckGo/Assets.xcassets/LogoText.imageset/Contents.json rename DuckDuckGo/Assets.xcassets/{LogoLightText.imageset => LogoText.imageset}/dark_portrait_logo.pdf (100%) rename DuckDuckGo/Assets.xcassets/{LogoDarkText.imageset => LogoText.imageset}/light_portrait_logo.pdf (100%) rename DuckDuckGo/Assets.xcassets/{LogoDarkText.imageset => LogoText.imageset}/updatedLogoGrayText2.pdf (100%) rename DuckDuckGo/Assets.xcassets/{LogoLightText.imageset => LogoText.imageset}/updatedLogoWhiteText2.pdf (100%) delete mode 100644 DuckDuckGo/Assets.xcassets/tabsToggleGrid-Dark.imageset/Contents.json rename DuckDuckGo/Assets.xcassets/{tabsToggleGrid-Light.imageset => tabsToggleGrid.imageset}/Contents.json (53%) rename DuckDuckGo/Assets.xcassets/{tabsToggleGrid-Dark.imageset => tabsToggleGrid.imageset}/tabsToggleGrid-Dark.pdf (100%) rename DuckDuckGo/Assets.xcassets/{tabsToggleGrid-Light.imageset => tabsToggleGrid.imageset}/tabsToggleGrid-Light.pdf (100%) delete mode 100644 DuckDuckGo/Assets.xcassets/tabsToggleList-Dark.imageset/Contents.json rename DuckDuckGo/Assets.xcassets/{tabsToggleList-Light.imageset => tabsToggleList.imageset}/Contents.json (53%) rename DuckDuckGo/Assets.xcassets/{tabsToggleList-Dark.imageset => tabsToggleList.imageset}/tabsToggleList-Dark.pdf (100%) rename DuckDuckGo/Assets.xcassets/{tabsToggleList-Light.imageset => tabsToggleList.imageset}/tabsToggleList-Light.pdf (100%) delete mode 100644 DuckDuckGo/DarkTheme.swift create mode 100644 DuckDuckGo/DefaultTheme.swift delete mode 100644 DuckDuckGo/LightTheme.swift delete mode 100644 DuckDuckGo/SyncSettingsViewController+Themable.swift diff --git a/Core/UIViewControllerExtension.swift b/Core/UIViewControllerExtension.swift index 4e787b286b..0c680622cf 100644 --- a/Core/UIViewControllerExtension.swift +++ b/Core/UIViewControllerExtension.swift @@ -34,19 +34,10 @@ extension UIViewController { return [] } - func overrideUserInterfaceStyle() { - if ThemeManager.shared.currentTheme.currentImageSet == .dark { - overrideUserInterfaceStyle = .dark - } else { - overrideUserInterfaceStyle = .light - } - } - public func presentShareSheet(withItems activityItems: [Any], fromButtonItem buttonItem: UIBarButtonItem, completion: UIActivityViewController.CompletionWithItemsHandler? = nil) { let activities = buildActivities() let shareController = UIActivityViewController(activityItems: activityItems, applicationActivities: activities) shareController.completionWithItemsHandler = completion - shareController.overrideUserInterfaceStyle() present(controller: shareController, fromButtonItem: buttonItem) } @@ -56,8 +47,6 @@ extension UIViewController { shareController.completionWithItemsHandler = completion if let overrideInterfaceStyle { shareController.overrideUserInterfaceStyle = overrideInterfaceStyle - } else { - shareController.overrideUserInterfaceStyle() } shareController.excludedActivityTypes = [.markupAsPDF] present(controller: shareController, fromView: sourceView, atPoint: point) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 4f16f1dc8c..6e4aff14a0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -317,6 +317,7 @@ 4BFB911B29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */; }; 6AC6DAB328804F97002723C0 /* BarsAnimator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */; }; 6AC98419288055C1005FA9CA /* BarsAnimatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */; }; + 6F655BE22BAB289E00AC3597 /* DefaultTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */; }; 6FDA1FB32B59584400AC962A /* AddressDisplayHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */; }; 83004E802193BB8200DA013C /* WKNavigationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */; }; 83004E862193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E852193E5ED00DA013C /* TabViewControllerBrowsingMenuExtension.swift */; }; @@ -344,7 +345,6 @@ 850250B520D80419002199C7 /* AtbAndVariantCleanupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850250B420D80419002199C7 /* AtbAndVariantCleanupTests.swift */; }; 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850365F223DE087800D0F787 /* UIImageViewExtension.swift */; }; 85047B8A1F69692C002A95D8 /* contentblocker.js in Resources */ = {isa = PBXBuildFile; fileRef = 85047B891F69692C002A95D8 /* contentblocker.js */; }; - 85047C752A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85047C742A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift */; }; 85047C772A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85047C762A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift */; }; 850559C923C61B5D0055C0D5 /* login-form-detection.js in Resources */ = {isa = PBXBuildFile; fileRef = 850559C823C61B5D0055C0D5 /* login-form-detection.js */; }; 850559D023CF647C0055C0D5 /* PreserveLogins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850559CF23CF647C0055C0D5 /* PreserveLogins.swift */; }; @@ -632,8 +632,6 @@ 98EF177D21837E35006750C1 /* new_tab_dark.json in Resources */ = {isa = PBXBuildFile; fileRef = 98EF177C21837E35006750C1 /* new_tab_dark.json */; }; 98F0FC2021FF18E700CE77AB /* AutoClearSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F0FC1F21FF18E700CE77AB /* AutoClearSettingsViewController.swift */; }; 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1D7217B37010011A0D4 /* Theme.swift */; }; - 98F3A1DA217B37200011A0D4 /* LightTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1D9217B37200011A0D4 /* LightTheme.swift */; }; - 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F3A1DB217B373E0011A0D4 /* DarkTheme.swift */; }; 98F6EA472863124100720957 /* ContentBlockerRulesLists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */; }; 98F78B8E22419093007CACF4 /* ThemableNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */; }; 9F8FE9492BAE50E50071E372 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9F8FE9482BAE50E50071E372 /* Lottie */; }; @@ -1434,6 +1432,7 @@ 4BFB911A29B7D9530014D4B7 /* AppTrackingProtectionStoringModelPerformanceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTrackingProtectionStoringModelPerformanceTests.swift; sourceTree = ""; }; 6AC6DAB228804F97002723C0 /* BarsAnimator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimator.swift; sourceTree = ""; }; 6AC98418288055C1005FA9CA /* BarsAnimatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BarsAnimatorTests.swift; sourceTree = ""; }; + 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTheme.swift; sourceTree = ""; }; 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Configuration.xcconfig; path = Configuration/Configuration.xcconfig; sourceTree = ""; }; 6FDA1FB22B59584400AC962A /* AddressDisplayHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddressDisplayHelper.swift; sourceTree = ""; }; 83004E7F2193BB8200DA013C /* WKNavigationExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WKNavigationExtension.swift; sourceTree = ""; }; @@ -1477,7 +1476,6 @@ 850250B420D80419002199C7 /* AtbAndVariantCleanupTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AtbAndVariantCleanupTests.swift; sourceTree = ""; }; 850365F223DE087800D0F787 /* UIImageViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImageViewExtension.swift; sourceTree = ""; }; 85047B891F69692C002A95D8 /* contentblocker.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; path = contentblocker.js; sourceTree = ""; }; - 85047C742A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+Themable.swift"; sourceTree = ""; }; 85047C762A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SyncSettingsViewController+SyncDelegate.swift"; sourceTree = ""; }; 850559C823C61B5D0055C0D5 /* login-form-detection.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "login-form-detection.js"; sourceTree = ""; }; 850559CF23CF647C0055C0D5 /* PreserveLogins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreserveLogins.swift; sourceTree = ""; }; @@ -2302,8 +2300,6 @@ 98F02E87251EAC11002A6C60 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = ""; }; 98F0FC1F21FF18E700CE77AB /* AutoClearSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoClearSettingsViewController.swift; sourceTree = ""; }; 98F3A1D7217B37010011A0D4 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; - 98F3A1D9217B37200011A0D4 /* LightTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LightTheme.swift; sourceTree = ""; }; - 98F3A1DB217B373E0011A0D4 /* DarkTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DarkTheme.swift; sourceTree = ""; }; 98F6EA462863124100720957 /* ContentBlockerRulesLists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesLists.swift; sourceTree = ""; }; 98F78B8D22419093007CACF4 /* ThemableNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemableNavigationController.swift; sourceTree = ""; }; AA3D854423D9942200788410 /* AppIconSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconSettingsViewController.swift; sourceTree = ""; }; @@ -4141,7 +4137,6 @@ children = ( 85582DFF29D7409700E9AE35 /* SyncSettingsViewController+PDFRendering.swift */, 85F98F91296F32BD00742F4A /* SyncSettingsViewController.swift */, - 85047C742A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift */, 85047C762A0D5D3D00D2FF3F /* SyncSettingsViewController+SyncDelegate.swift */, ); name = Controllers; @@ -4313,9 +4308,8 @@ 98F3A1D7217B37010011A0D4 /* Theme.swift */, 9874F9ED2187AFCE00CAF33D /* Themable.swift */, 98DA6EC92181E41F00E65433 /* ThemeManager.swift */, - 98F3A1D9217B37200011A0D4 /* LightTheme.swift */, - 98F3A1DB217B373E0011A0D4 /* DarkTheme.swift */, 8536A1FC2ACF114B003AC5BA /* Theme+DesignSystem.swift */, + 6F655BE12BAB289E00AC3597 /* DefaultTheme.swift */, ); name = Themes; sourceTree = ""; @@ -6631,7 +6625,6 @@ 8546A54A2A672959003929BF /* MainViewController+Email.swift in Sources */, F4F6DFB226E6AEC100ED7E12 /* AddOrEditBookmarkViewController.swift in Sources */, EE458D0D2AB1DA4600FC651A /* EventMapping+NetworkProtectionError.swift in Sources */, - 85047C752A0D3C2900D2FF3F /* SyncSettingsViewController+Themable.swift in Sources */, F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */, C1BF0BA529B63D7200482B73 /* AutofillLoginPromptHelper.swift in Sources */, D664C7C92B289AA200CBFA76 /* AsyncHeadlessWebView.swift in Sources */, @@ -6765,7 +6758,6 @@ EEF0F8CC2ABC832300630031 /* NetworkProtectionDebugFeatures.swift in Sources */, B60DFF072872B64B0061E7C2 /* JSAlertController.swift in Sources */, 981FED6E22025151008488D7 /* BlankSnapshotViewController.swift in Sources */, - 98F3A1DC217B373E0011A0D4 /* DarkTheme.swift in Sources */, D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */, 851B128822200575004781BC /* Onboarding.swift in Sources */, 3151F0EE2735800800226F58 /* VoiceSearchFeedbackView.swift in Sources */, @@ -6899,6 +6891,7 @@ D6E0C1872B7A2D0700D5E1E9 /* DesktopDownloadViewButtonStyle.swift in Sources */, 854A012B2A54412600FCC628 /* ActivityViewController.swift in Sources */, F1CA3C391F045885005FADB3 /* PrivacyUserDefaults.swift in Sources */, + 6F655BE22BAB289E00AC3597 /* DefaultTheme.swift in Sources */, BD862E072B30F5E30073E2EE /* VPNFeedbackSender.swift in Sources */, AA4D6A6A23DB87B1007E8790 /* AppIconManager.swift in Sources */, 8563A03C1F9288D600F04442 /* BrowserChromeManager.swift in Sources */, @@ -6998,7 +6991,6 @@ B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */, 0290471E29E708750008FE3C /* AppTPManageTrackersView.swift in Sources */, F16390821E648B7A005B4550 /* HomeViewController.swift in Sources */, - 98F3A1DA217B37200011A0D4 /* LightTheme.swift in Sources */, 985892522260B1B200EEB31B /* ProgressView.swift in Sources */, 85BA585A1F3506AE00C6E8CA /* AppSettings.swift in Sources */, 3151F0EA27357FBA00226F58 /* SpeechRecognizer.swift in Sources */, diff --git a/DuckDuckGo/AddOrEditBookmarkViewController.swift b/DuckDuckGo/AddOrEditBookmarkViewController.swift index 188764d2ba..827efd99cf 100644 --- a/DuckDuckGo/AddOrEditBookmarkViewController.swift +++ b/DuckDuckGo/AddOrEditBookmarkViewController.swift @@ -92,7 +92,8 @@ class AddOrEditBookmarkViewController: UIViewController { updateTitle() updateSaveButton() - applyTheme(ThemeManager.shared.currentTheme) + decorateNavigationBar() + decorateToolbar() viewModelCancellable = viewModel.externalUpdates.sink { [weak self] _ in self?.foldersViewController?.refresh() @@ -190,13 +191,3 @@ extension AddOrEditBookmarkViewController: AddOrEditBookmarkViewControllerDelega } } - -extension AddOrEditBookmarkViewController: Themable { - - func decorate(with theme: Theme) { - decorateNavigationBar(with: theme) - decorateToolbar(with: theme) - - overrideSystemTheme(with: theme) - } -} diff --git a/DuckDuckGo/AppIconSettingsCell.swift b/DuckDuckGo/AppIconSettingsCell.swift index fc7c137b21..0afd421d12 100644 --- a/DuckDuckGo/AppIconSettingsCell.swift +++ b/DuckDuckGo/AppIconSettingsCell.swift @@ -35,6 +35,7 @@ class AppIconSettingsCell: UICollectionViewCell { super.init(coder: coder) isAccessibilityElement = true accessibilityTraits.insert(.button) + decorate() } override var isSelected: Bool { @@ -42,13 +43,19 @@ class AppIconSettingsCell: UICollectionViewCell { layer.borderWidth = isSelected ? 2.0 : 0.0 } } - } -extension AppIconSettingsCell: Themable { - - func decorate(with theme: Theme) { +extension AppIconSettingsCell { + private func decorate() { + let theme = ThemeManager.shared.currentTheme layer.borderColor = theme.iconCellBorderColor.cgColor } + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + decorate() + } + } } diff --git a/DuckDuckGo/AppIconSettingsViewController.swift b/DuckDuckGo/AppIconSettingsViewController.swift index e80e9c69f6..8effb6bbdb 100644 --- a/DuckDuckGo/AppIconSettingsViewController.swift +++ b/DuckDuckGo/AppIconSettingsViewController.swift @@ -29,7 +29,7 @@ class AppIconSettingsViewController: UICollectionViewController { super.viewDidLoad() collectionView.dataSource = dataSource - applyTheme(ThemeManager.shared.currentTheme) + decorate() } private func initSelection() { @@ -66,7 +66,6 @@ class AppIconDataSource: NSObject, UICollectionViewDataSource { as? AppIconSettingsCell else { fatalError("Expected IconSettingsCell") } - cell.decorate(with: ThemeManager.shared.currentTheme) let appIcon = appIcons[indexPath.row] cell.appIcon = appIcon @@ -95,9 +94,10 @@ class AppIconWorker { } -extension AppIconSettingsViewController: Themable { +extension AppIconSettingsViewController { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme collectionView.backgroundColor = theme.backgroundColor collectionView.reloadData() initSelection() diff --git a/DuckDuckGo/Assets.xcassets/ErrorInfoDark.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/ErrorInfoDark.imageset/Contents.json deleted file mode 100644 index 73f512d9a6..0000000000 --- a/DuckDuckGo/Assets.xcassets/ErrorInfoDark.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "errorImgDark.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DuckDuckGo/Assets.xcassets/ErrorInfoLight.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/ErrorInfoLight.imageset/Contents.json deleted file mode 100644 index 7c77a84b81..0000000000 --- a/DuckDuckGo/Assets.xcassets/ErrorInfoLight.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "errorImgLight.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - }, - "properties" : { - "template-rendering-intent" : "original" - } -} \ No newline at end of file diff --git a/DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/Contents.json new file mode 100644 index 0000000000..3e999bf806 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/Contents.json @@ -0,0 +1,25 @@ +{ + "images" : [ + { + "filename" : "errorImgLight.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "errorImgDark.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/DuckDuckGo/Assets.xcassets/ErrorInfoDark.imageset/errorImgDark.pdf b/DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/errorImgDark.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/ErrorInfoDark.imageset/errorImgDark.pdf rename to DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/errorImgDark.pdf diff --git a/DuckDuckGo/Assets.xcassets/ErrorInfoLight.imageset/errorImgLight.pdf b/DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/errorImgLight.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/ErrorInfoLight.imageset/errorImgLight.pdf rename to DuckDuckGo/Assets.xcassets/ErrorInfoUniversal.imageset/errorImgLight.pdf diff --git a/DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/Contents.json deleted file mode 100644 index c29cf298bd..0000000000 --- a/DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/Contents.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "light_portrait_logo.pdf" - }, - { - "idiom" : "universal", - "filename" : "updatedLogoGrayText2.pdf", - "height-class" : "compact" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/DuckDuckGo/Assets.xcassets/LogoLightText.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/LogoLightText.imageset/Contents.json deleted file mode 100644 index d4d281e3a0..0000000000 --- a/DuckDuckGo/Assets.xcassets/LogoLightText.imageset/Contents.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "images" : [ - { - "filename" : "dark_portrait_logo.pdf", - "idiom" : "universal" - }, - { - "filename" : "updatedLogoWhiteText2.pdf", - "height-class" : "compact", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/DuckDuckGo/Assets.xcassets/LogoText.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/LogoText.imageset/Contents.json new file mode 100644 index 0000000000..e2be879966 --- /dev/null +++ b/DuckDuckGo/Assets.xcassets/LogoText.imageset/Contents.json @@ -0,0 +1,38 @@ +{ + "images" : [ + { + "filename" : "light_portrait_logo.pdf", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "dark_portrait_logo.pdf", + "idiom" : "universal" + }, + { + "filename" : "updatedLogoGrayText2.pdf", + "height-class" : "compact", + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "updatedLogoWhiteText2.pdf", + "height-class" : "compact", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/Assets.xcassets/LogoLightText.imageset/dark_portrait_logo.pdf b/DuckDuckGo/Assets.xcassets/LogoText.imageset/dark_portrait_logo.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/LogoLightText.imageset/dark_portrait_logo.pdf rename to DuckDuckGo/Assets.xcassets/LogoText.imageset/dark_portrait_logo.pdf diff --git a/DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/light_portrait_logo.pdf b/DuckDuckGo/Assets.xcassets/LogoText.imageset/light_portrait_logo.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/light_portrait_logo.pdf rename to DuckDuckGo/Assets.xcassets/LogoText.imageset/light_portrait_logo.pdf diff --git a/DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/updatedLogoGrayText2.pdf b/DuckDuckGo/Assets.xcassets/LogoText.imageset/updatedLogoGrayText2.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/LogoDarkText.imageset/updatedLogoGrayText2.pdf rename to DuckDuckGo/Assets.xcassets/LogoText.imageset/updatedLogoGrayText2.pdf diff --git a/DuckDuckGo/Assets.xcassets/LogoLightText.imageset/updatedLogoWhiteText2.pdf b/DuckDuckGo/Assets.xcassets/LogoText.imageset/updatedLogoWhiteText2.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/LogoLightText.imageset/updatedLogoWhiteText2.pdf rename to DuckDuckGo/Assets.xcassets/LogoText.imageset/updatedLogoWhiteText2.pdf diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Dark.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Dark.imageset/Contents.json deleted file mode 100644 index 66cc1cf77d..0000000000 --- a/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Dark.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "tabsToggleGrid-Dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Light.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/Contents.json similarity index 53% rename from DuckDuckGo/Assets.xcassets/tabsToggleGrid-Light.imageset/Contents.json rename to DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/Contents.json index 795786a1b3..89e3de9717 100644 --- a/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Light.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/Contents.json @@ -3,6 +3,16 @@ { "filename" : "tabsToggleGrid-Light.pdf", "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tabsToggleGrid-Dark.pdf", + "idiom" : "universal" } ], "info" : { diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Dark.imageset/tabsToggleGrid-Dark.pdf b/DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/tabsToggleGrid-Dark.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/tabsToggleGrid-Dark.imageset/tabsToggleGrid-Dark.pdf rename to DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/tabsToggleGrid-Dark.pdf diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleGrid-Light.imageset/tabsToggleGrid-Light.pdf b/DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/tabsToggleGrid-Light.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/tabsToggleGrid-Light.imageset/tabsToggleGrid-Light.pdf rename to DuckDuckGo/Assets.xcassets/tabsToggleGrid.imageset/tabsToggleGrid-Light.pdf diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleList-Dark.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/tabsToggleList-Dark.imageset/Contents.json deleted file mode 100644 index cd4cb82e92..0000000000 --- a/DuckDuckGo/Assets.xcassets/tabsToggleList-Dark.imageset/Contents.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "images" : [ - { - "filename" : "tabsToggleList-Dark.pdf", - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - }, - "properties" : { - "preserves-vector-representation" : true - } -} diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleList-Light.imageset/Contents.json b/DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/Contents.json similarity index 53% rename from DuckDuckGo/Assets.xcassets/tabsToggleList-Light.imageset/Contents.json rename to DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/Contents.json index 1009e215b0..fb6ad68ad2 100644 --- a/DuckDuckGo/Assets.xcassets/tabsToggleList-Light.imageset/Contents.json +++ b/DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/Contents.json @@ -3,6 +3,16 @@ { "filename" : "tabsToggleList-Light.pdf", "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "tabsToggleList-Dark.pdf", + "idiom" : "universal" } ], "info" : { diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleList-Dark.imageset/tabsToggleList-Dark.pdf b/DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/tabsToggleList-Dark.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/tabsToggleList-Dark.imageset/tabsToggleList-Dark.pdf rename to DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/tabsToggleList-Dark.pdf diff --git a/DuckDuckGo/Assets.xcassets/tabsToggleList-Light.imageset/tabsToggleList-Light.pdf b/DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/tabsToggleList-Light.pdf similarity index 100% rename from DuckDuckGo/Assets.xcassets/tabsToggleList-Light.imageset/tabsToggleList-Light.pdf rename to DuckDuckGo/Assets.xcassets/tabsToggleList.imageset/tabsToggleList-Light.pdf diff --git a/DuckDuckGo/AuthenticationViewController.swift b/DuckDuckGo/AuthenticationViewController.swift index 0538cb3ccb..d983a309b3 100644 --- a/DuckDuckGo/AuthenticationViewController.swift +++ b/DuckDuckGo/AuthenticationViewController.swift @@ -43,7 +43,7 @@ class AuthenticationViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() hideUnlockInstructions() - applyTheme(ThemeManager.shared.currentTheme) + decorate() } override var supportedInterfaceOrientations: UIInterfaceOrientationMask { @@ -98,16 +98,10 @@ class AuthenticationViewController: UIViewController { } } -extension AuthenticationViewController: Themable { +extension AuthenticationViewController { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme view.backgroundColor = theme.backgroundColor - - switch theme.currentImageSet { - case .light: - logo?.image = UIImage(named: "LogoDarkText") - case .dark: - logo?.image = UIImage(named: "LogoLightText") - } } } diff --git a/DuckDuckGo/AutoClearSettingsViewController.swift b/DuckDuckGo/AutoClearSettingsViewController.swift index 77fb3173ce..5b5ec0c874 100644 --- a/DuckDuckGo/AutoClearSettingsViewController.swift +++ b/DuckDuckGo/AutoClearSettingsViewController.swift @@ -42,8 +42,8 @@ class AutoClearSettingsViewController: UITableViewController { clearDataSettings = loadClearDataSettings() configureClearDataToggle() tableView.reloadData() - - applyTheme(ThemeManager.shared.currentTheme) + + decorate() } private func loadClearDataSettings() -> AutoClearSettingsModel? { @@ -154,9 +154,10 @@ class AutoClearSettingsViewController: UITableViewController { } } -extension AutoClearSettingsViewController: Themable { +extension AutoClearSettingsViewController { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme for label in labels { label.textColor = theme.tableCellTextColor diff --git a/DuckDuckGo/AutocompleteViewController.swift b/DuckDuckGo/AutocompleteViewController.swift index 8f57da2bb2..a2fc59be5f 100644 --- a/DuckDuckGo/AutocompleteViewController.swift +++ b/DuckDuckGo/AutocompleteViewController.swift @@ -96,7 +96,7 @@ class AutocompleteViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() configureTableView() - applyTheme(ThemeManager.shared.currentTheme) + decorate() queryDebounceCancellable = $query .debounce(for: .milliseconds(Constants.debounceDelay), scheduler: RunLoop.main) @@ -137,11 +137,6 @@ class AutocompleteViewController: UIViewController { navigationController?.hidesBarsOnSwipe = hidesBarsOnSwipeDefault } - override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { - super.traitCollectionDidChange(previousTraitCollection) - tableView.reloadData() - } - func updateQuery(query: String) { selectedItem = -1 cancelInFlightRequests() @@ -302,10 +297,10 @@ extension AutocompleteViewController: UIGestureRecognizerDelegate { } } -extension AutocompleteViewController: Themable { - func decorate(with theme: Theme) { +extension AutocompleteViewController { + private func decorate() { + let theme = ThemeManager.shared.currentTheme tableView.separatorColor = theme.tableCellSeparatorColor - tableView.reloadData() } } diff --git a/DuckDuckGo/AutoconsentSettingsViewController.swift b/DuckDuckGo/AutoconsentSettingsViewController.swift index 172be442e2..f25b7eaff8 100644 --- a/DuckDuckGo/AutoconsentSettingsViewController.swift +++ b/DuckDuckGo/AutoconsentSettingsViewController.swift @@ -35,8 +35,8 @@ final class AutoconsentSettingsViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() - applyTheme(ThemeManager.shared.currentTheme) - + decorate() + autoconsentToggle.isOn = appSettings.autoconsentEnabled let fontSize = FontSettings.fontSizeForHeaderView @@ -61,11 +61,6 @@ final class AutoconsentSettingsViewController: UITableViewController { } } - override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { - let theme = ThemeManager.shared.currentTheme - cell.decorate(with: theme) - } - @IBAction private func onAutoconsentValueChanged(_ sender: Any) { appSettings.autoconsentEnabled = autoconsentToggle.isOn Pixel.fire(pixel: autoconsentToggle.isOn ? .settingsAutoconsentOn : .settingsAutoconsentOff) @@ -73,9 +68,10 @@ final class AutoconsentSettingsViewController: UITableViewController { } -extension AutoconsentSettingsViewController: Themable { +extension AutoconsentSettingsViewController { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme infoText.textColor = theme.tableHeaderTextColor diff --git a/DuckDuckGo/AutofillEmptySearchView.swift b/DuckDuckGo/AutofillEmptySearchView.swift index 3fad1ce258..65b01d80c6 100644 --- a/DuckDuckGo/AutofillEmptySearchView.swift +++ b/DuckDuckGo/AutofillEmptySearchView.swift @@ -64,6 +64,7 @@ class AutofillEmptySearchView: UIView { super.init(frame: frame) installSubviews() installConstraints() + decorate() } required init?(coder: NSCoder) { @@ -86,9 +87,10 @@ class AutofillEmptySearchView: UIView { } } -extension AutofillEmptySearchView: Themable { +extension AutofillEmptySearchView { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme title.textColor = theme.autofillEmptySearchViewTextColor subtitle.textColor = theme.autofillEmptySearchViewTextColor } diff --git a/DuckDuckGo/AutofillItemsEmptyView.swift b/DuckDuckGo/AutofillItemsEmptyView.swift index 6ef8c058cb..d081a67f0f 100644 --- a/DuckDuckGo/AutofillItemsEmptyView.swift +++ b/DuckDuckGo/AutofillItemsEmptyView.swift @@ -33,6 +33,7 @@ class AutofillItemsEmptyView: UIView { super.init(frame: frame) installSubviews() installConstraints() + decorate() } required init?(coder: NSCoder) { @@ -115,9 +116,10 @@ class AutofillItemsEmptyView: UIView { } } -extension AutofillItemsEmptyView: Themable { +extension AutofillItemsEmptyView { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme title.textColor = theme.autofillDefaultTitleTextColor } } diff --git a/DuckDuckGo/AutofillLoginDetailsViewController.swift b/DuckDuckGo/AutofillLoginDetailsViewController.swift index 803e5d1cce..2fde29ee32 100644 --- a/DuckDuckGo/AutofillLoginDetailsViewController.swift +++ b/DuckDuckGo/AutofillLoginDetailsViewController.swift @@ -103,7 +103,7 @@ class AutofillLoginDetailsViewController: UIViewController { installSubviews() setupCancellables() setupTableViewAppearance() - applyTheme(ThemeManager.shared.currentTheme) + decorate() installConstraints() configureNotifications() setupNavigationBar() @@ -332,13 +332,12 @@ extension AutofillLoginDetailsViewController: AutofillLoginDetailsViewModelDeleg // MARK: Themable -extension AutofillLoginDetailsViewController: Themable { +extension AutofillLoginDetailsViewController { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme lockedView.backgroundColor = theme.backgroundColor - noAuthAvailableView.decorate(with: theme) - view.backgroundColor = theme.backgroundColor navigationController?.navigationBar.barTintColor = theme.barBackgroundColor diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index 7b90ad5868..84465c646b 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -166,7 +166,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { setupCancellables() installSubviews() installConstraints() - applyTheme(ThemeManager.shared.currentTheme) + decorate() updateViewState() configureNotification() registerForKeyboardNotifications() @@ -867,12 +867,10 @@ extension AutofillLoginSettingsListViewController: EnableAutofillSettingsTableVi // MARK: Themable -extension AutofillLoginSettingsListViewController: Themable { +extension AutofillLoginSettingsListViewController { - func decorate(with theme: Theme) { - emptyView.decorate(with: theme) - emptySearchView.decorate(with: theme) - noAuthAvailableView.decorate(with: theme) + private func decorate() { + let theme = ThemeManager.shared.currentTheme view.backgroundColor = theme.backgroundColor tableView.backgroundColor = theme.backgroundColor diff --git a/DuckDuckGo/AutofillNoAuthAvailableView.swift b/DuckDuckGo/AutofillNoAuthAvailableView.swift index 1db32fd310..8729d39c81 100644 --- a/DuckDuckGo/AutofillNoAuthAvailableView.swift +++ b/DuckDuckGo/AutofillNoAuthAvailableView.swift @@ -25,6 +25,7 @@ class AutofillNoAuthAvailableView: UIView { super.init(frame: frame) installSubviews() installConstraints() + decorate() } required init?(coder: NSCoder) { @@ -174,9 +175,10 @@ class AutofillNoAuthAvailableView: UIView { } } -extension AutofillNoAuthAvailableView: Themable { +extension AutofillNoAuthAvailableView { - func decorate(with theme: Theme) { + private func decorate() { + let theme = ThemeManager.shared.currentTheme title.textColor = theme.autofillDefaultTitleTextColor subtitle.textColor = theme.autofillDefaultSubtitleTextColor } diff --git a/DuckDuckGo/Base.lproj/Authentication.storyboard b/DuckDuckGo/Base.lproj/Authentication.storyboard index 5bba576fcf..bae34d3455 100644 --- a/DuckDuckGo/Base.lproj/Authentication.storyboard +++ b/DuckDuckGo/Base.lproj/Authentication.storyboard @@ -1,11 +1,9 @@ - - - - + + - + @@ -21,20 +19,20 @@ - - + + - + @@ -83,6 +81,6 @@ - + diff --git a/DuckDuckGo/Base.lproj/Bookmarks.storyboard b/DuckDuckGo/Base.lproj/Bookmarks.storyboard index cf28e50f78..84b847ab71 100644 --- a/DuckDuckGo/Base.lproj/Bookmarks.storyboard +++ b/DuckDuckGo/Base.lproj/Bookmarks.storyboard @@ -1,9 +1,9 @@ - + - + @@ -63,10 +63,10 @@ - + - + @@ -361,10 +361,10 @@ - + - + diff --git a/DuckDuckGo/Base.lproj/Feedback.storyboard b/DuckDuckGo/Base.lproj/Feedback.storyboard index ac8172e1c5..504755ce55 100644 --- a/DuckDuckGo/Base.lproj/Feedback.storyboard +++ b/DuckDuckGo/Base.lproj/Feedback.storyboard @@ -1,17 +1,17 @@ - + - + - + - + @@ -147,7 +147,7 @@ - + @@ -422,8 +422,8 @@ - - + + diff --git a/DuckDuckGo/Base.lproj/Tab.storyboard b/DuckDuckGo/Base.lproj/Tab.storyboard index 203637fe2c..4f3852b007 100644 --- a/DuckDuckGo/Base.lproj/Tab.storyboard +++ b/DuckDuckGo/Base.lproj/Tab.storyboard @@ -1,11 +1,10 @@ - - + + - + - @@ -48,29 +47,29 @@ - + @@ -174,15 +173,6 @@ - - - - - - - - - - + diff --git a/DuckDuckGo/Base.lproj/TabSwitcher.storyboard b/DuckDuckGo/Base.lproj/TabSwitcher.storyboard index 61934fab72..ede7c1d7f7 100644 --- a/DuckDuckGo/Base.lproj/TabSwitcher.storyboard +++ b/DuckDuckGo/Base.lproj/TabSwitcher.storyboard @@ -1,9 +1,9 @@ - + - + @@ -19,7 +19,7 @@ - + @@ -102,7 +102,7 @@ + - - - + + + - - - - + - - @@ -262,14 +219,13 @@ - - + - - + + diff --git a/DuckDuckGo/DaxOnboardingPadViewController.swift b/DuckDuckGo/DaxOnboardingPadViewController.swift index 7690b90385..3363680a4a 100644 --- a/DuckDuckGo/DaxOnboardingPadViewController.swift +++ b/DuckDuckGo/DaxOnboardingPadViewController.swift @@ -27,6 +27,7 @@ class DaxOnboardingPadViewController: UIViewController, Onboarding { if let navController = segue.destination as? UINavigationController, let onboarding = navController.viewControllers.first as? OnboardingViewController { onboarding.delegate = delegate + self.view.backgroundColor = onboarding.view.backgroundColor } } diff --git a/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Contents.json b/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Contents.json index 5922f77d1c..3476c31dce 100644 --- a/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Contents.json +++ b/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "illustration.pdf", + "filename" : "Default-Browser.svg", "idiom" : "universal" } ], diff --git a/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Default-Browser.svg b/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Default-Browser.svg new file mode 100644 index 0000000000..7e3c610e94 --- /dev/null +++ b/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/Default-Browser.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/illustration.pdf b/DuckDuckGo/Onboarding.xcassets/OnboardingDefaultBrowserImage.imageset/illustration.pdf deleted file mode 100644 index ace8b4f06c4f3cf25f08d6e8788218c35d49f577..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 111042 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

nj}1qaE+IUPJdRZLqSd4>giEEJ&#jP-ZFa=I^R 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 diff --git a/DuckDuckGo/OnboardingButtonsView.swift b/DuckDuckGo/OnboardingButtonsView.swift new file mode 100644 index 0000000000..60c71cde0e --- /dev/null +++ b/DuckDuckGo/OnboardingButtonsView.swift @@ -0,0 +1,56 @@ +// +// OnboardingButtonsView.swift +// DuckDuckGo +// +// 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 DuckUI + +struct OnboardingActions: View { + + @ObservedObject var viewModel: Model + + var primaryAction: (() -> Void)? + var secondaryAction: (() -> Void)? + + var body: some View { + VStack(spacing: 8) { + Button(action: { + self.primaryAction?() + }, label: { + Text(viewModel.primaryButtonTitle) + }) + .buttonStyle(PrimaryButtonStyle()) + .disabled(!viewModel.isContinueEnabled) + + Button(action: { + self.secondaryAction?() + }, label: { + Text(viewModel.secondaryButtonTitle) + }) + .buttonStyle(GhostButtonStyle()) + } + } +} + +extension OnboardingActions { + class Model: ObservableObject { + @Published var primaryButtonTitle = "" + @Published var secondaryButtonTitle = "" + @Published var isContinueEnabled = true + } +} diff --git a/DuckDuckGo/OnboardingDefaultBroswerViewController.swift b/DuckDuckGo/OnboardingDefaultBroswerViewController.swift index beb730753b..c6401b3d29 100644 --- a/DuckDuckGo/OnboardingDefaultBroswerViewController.swift +++ b/DuckDuckGo/OnboardingDefaultBroswerViewController.swift @@ -21,7 +21,7 @@ import UIKit import Core class OnboardingDefaultBroswerViewController: OnboardingContentViewController { - + override var header: String { return UserText.onboardingDefaultBrowserTitle } diff --git a/DuckDuckGo/OnboardingViewController.swift b/DuckDuckGo/OnboardingViewController.swift index 2f4bf65606..15b95b78ab 100644 --- a/DuckDuckGo/OnboardingViewController.swift +++ b/DuckDuckGo/OnboardingViewController.swift @@ -19,6 +19,7 @@ import UIKit import Core +import SwiftUI class OnboardingViewController: UIViewController, Onboarding { @@ -32,8 +33,12 @@ class OnboardingViewController: UIViewController, Onboarding { @IBOutlet weak var subheaderContainer: UIView! @IBOutlet weak var contentWidth: NSLayoutConstraint! @IBOutlet weak var contentContainer: UIView! - @IBOutlet weak var skipButton: UIButton! - @IBOutlet weak var continueButton: UIButton! + @IBOutlet weak var buttonsContainer: UIView! + + private let buttonsController = UIHostingController(rootView: OnboardingActions(viewModel: .init())) + private var buttonsModel: OnboardingActions.Model { + self.buttonsController.rootView.viewModel + } var contentController: OnboardingContentViewController? @@ -43,9 +48,11 @@ class OnboardingViewController: UIViewController, Onboarding { override func viewDidLoad() { super.viewDidLoad() + setUpButtons() loadInitialContent() updateForSmallerScreens() setUpNavigationBar() + decorate() } override func viewWillAppear(_ animated: Bool) { @@ -76,7 +83,33 @@ class OnboardingViewController: UIViewController, Onboarding { navigationController?.navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default) navigationController?.navigationBar.shadowImage = UIImage() } - + + private func setUpButtons() { + + buttonsController.rootView.primaryAction = { [weak self] in + self?.next(on: .continue) + } + + buttonsController.rootView.secondaryAction = { [weak self] in + self?.next(on: .skip) + } + + buttonsController.view.backgroundColor = .clear + + addChild(buttonsController) + buttonsContainer.addSubview(buttonsController.view) + buttonsController.didMove(toParent: self) + + let buttonsView = buttonsController.view! + buttonsView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + buttonsView.leadingAnchor.constraint(equalTo: buttonsContainer.leadingAnchor), + buttonsView.trailingAnchor.constraint(equalTo: buttonsContainer.trailingAnchor), + buttonsView.topAnchor.constraint(equalTo: buttonsContainer.topAnchor), + buttonsView.bottomAnchor.constraint(equalTo: buttonsContainer.bottomAnchor), + ]) + } + private func adjustHeight(label: UILabel, toMaxHeight maxHeight: CGFloat) -> CGFloat { guard var fontSize = label.attributedText?.font?.pointSize else { return label.bounds.height } @@ -107,33 +140,33 @@ class OnboardingViewController: UIViewController, Onboarding { private func updateContent(_ controller: OnboardingContentViewController) { controller.delegate = self - continueButton.isEnabled = controller.canContinue + buttonsModel.isContinueEnabled = controller.canContinue contentController = controller header.setAttributedTextString(controller.header) subheader.setAttributedTextString(controller.subtitle ?? "") UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: header) } - - @IBAction func next(sender: UIButton) { - + + private func next(on button: ActionButton) { let navigationHandler = { if let name = self.controllerNames.first, - let oldController = self.contentController, - let newController = self.storyboard?.instantiateViewController(withIdentifier: name) as? OnboardingContentViewController { - + let oldController = self.contentController, + let newController = self.storyboard?.instantiateViewController(withIdentifier: name) as? OnboardingContentViewController { + self.transition(from: oldController, to: newController) } else { self.done() } } - if sender == continueButton { + switch button { + case .continue: contentController?.onContinuePressed(navigationHandler: navigationHandler) - } else { + case .skip: contentController?.onSkipPressed(navigationHandler: navigationHandler) } } - + private func transition(from oldController: OnboardingContentViewController, to newController: OnboardingContentViewController) { let frame = oldController.view.frame @@ -170,15 +203,11 @@ class OnboardingViewController: UIViewController, Onboarding { private func prepareFor(nextScreen: OnboardingContentViewController) { controllerNames = [String](controllerNames.dropFirst()) - - let continueButtonTitle = nextScreen.continueButtonTitle - continueButton.setTitle(continueButtonTitle, for: .normal) - continueButton.setTitle(continueButtonTitle, for: .disabled) - continueButton.isEnabled = nextScreen.canContinue - - let skipButtonTitle = nextScreen.skipButtonTitle - skipButton.setTitle(skipButtonTitle, for: .normal) - skipButton.setTitle(skipButtonTitle, for: .disabled) + + buttonsModel.primaryButtonTitle = nextScreen.continueButtonTitle + buttonsModel.isContinueEnabled = nextScreen.canContinue + + buttonsModel.secondaryButtonTitle = nextScreen.skipButtonTitle } func done() { @@ -202,7 +231,24 @@ class OnboardingViewController: UIViewController, Onboarding { extension OnboardingViewController: OnboardingContentDelegate { func setContinueEnabled(_ enabled: Bool) { - continueButton.isEnabled = enabled + buttonsModel.isContinueEnabled = enabled } } + +extension OnboardingViewController { + private func decorate() { + let theme = ThemeManager.shared.currentTheme + + view.backgroundColor = theme.onboardingBackgroundColor + header.textColor = theme.onboardingHeaderColor + subheader.textColor = theme.onboardingSubheaderColor + } +} + +private extension OnboardingViewController { + enum ActionButton { + case `continue` + case skip + } +} diff --git a/DuckDuckGo/Theme+DesignSystem.swift b/DuckDuckGo/Theme+DesignSystem.swift index 4d5ccd89bc..a98dfdbb0a 100644 --- a/DuckDuckGo/Theme+DesignSystem.swift +++ b/DuckDuckGo/Theme+DesignSystem.swift @@ -91,4 +91,7 @@ extension Theme { // No design system colour yet, so fall back to SDK colours var tableCellAccessoryTextColor: UIColor { .secondaryLabel } + var onboardingBackgroundColor: UIColor { UIColor(designSystemColor: .surface) } + var onboardingHeaderColor: UIColor { UIColor(designSystemColor: .textPrimary) } + var onboardingSubheaderColor: UIColor { UIColor(designSystemColor: .textSecondary) } } diff --git a/DuckDuckGo/Theme.swift b/DuckDuckGo/Theme.swift index 8ee5e0fea2..8533cf8c8f 100644 --- a/DuckDuckGo/Theme.swift +++ b/DuckDuckGo/Theme.swift @@ -132,4 +132,7 @@ protocol Theme { var privacyDashboardWebviewBackgroundColor: UIColor { get } + var onboardingBackgroundColor: UIColor { get } + var onboardingHeaderColor: UIColor { get } + var onboardingSubheaderColor: UIColor { get } } diff --git a/LocalPackages/DuckUI/Sources/DuckUI/Button.swift b/LocalPackages/DuckUI/Sources/DuckUI/Button.swift index d87ff913b5..720ebd4386 100644 --- a/LocalPackages/DuckUI/Sources/DuckUI/Button.swift +++ b/LocalPackages/DuckUI/Sources/DuckUI/Button.swift @@ -31,11 +31,13 @@ public struct PrimaryButtonStyle: ButtonStyle { } public func makeBody(configuration: Configuration) -> some View { - let isLight = colorScheme == .light - let standardBackgroundColor = isLight ? Color.blueBase : Color.blue30 - let disabledBackgroundColor = isLight ? Color.black.opacity(0.06) : Color.white.opacity(0.18) - let standardForegroundColor = isLight ? Color.white : Color.black.opacity(0.84) - let disabledForegroundColor = isLight ? Color.black.opacity(0.36) : Color.white.opacity(0.36) + let isDark = colorScheme == .dark + let standardBackgroundColor = isDark ? Color.blue30 : Color.blueBase + let pressedBackgroundColor = isDark ? Color.blueBase : Color.blue70 + let disabledBackgroundColor = isDark ? Color.white.opacity(0.18) : Color.black.opacity(0.06) + let standardForegroundColor = isDark ? Color.black.opacity(0.84) : Color.white + let pressedForegroundColor = isDark ? Color.black.opacity(0.84) : Color.white + let disabledForegroundColor = isDark ? Color.white.opacity(0.36) : Color.black.opacity(0.36) let backgroundColor = disabled ? disabledBackgroundColor : standardBackgroundColor let foregroundColor = disabled ? disabledForegroundColor : standardForegroundColor @@ -44,10 +46,10 @@ public struct PrimaryButtonStyle: ButtonStyle { .multilineTextAlignment(.center) .lineLimit(nil) .font(Font(UIFont.boldAppFont(ofSize: compact ? Consts.fontSize - 1 : Consts.fontSize))) - .foregroundColor(configuration.isPressed ? standardForegroundColor.opacity(Consts.pressedOpacity) : foregroundColor) + .foregroundColor(configuration.isPressed ? pressedForegroundColor : foregroundColor) .padding() .frame(minWidth: 0, maxWidth: .infinity, maxHeight: compact ? Consts.height - 10 : Consts.height) - .background(configuration.isPressed ? standardBackgroundColor.opacity(Consts.pressedOpacity) : backgroundColor) + .background(configuration.isPressed ? pressedBackgroundColor : backgroundColor) .cornerRadius(Consts.cornerRadius) } } @@ -90,25 +92,47 @@ public struct SecondaryButtonStyle: ButtonStyle { public struct GhostButtonStyle: ButtonStyle { @Environment(\.colorScheme) private var colorScheme - + public init() {} - private var foregroundColor: Color { - colorScheme == .light ? .blueBase : .white - } public func makeBody(configuration: Configuration) -> some View { configuration.label .font(Font(UIFont.boldAppFont(ofSize: Consts.fontSize))) - .foregroundColor(configuration.isPressed ? foregroundColor.opacity(Consts.pressedOpacity) : foregroundColor.opacity(1)) + .foregroundColor(foregroundColor(configuration.isPressed)) .padding() .frame(minWidth: 0, maxWidth: .infinity, maxHeight: Consts.height) - .background(Color.clear) + .background(backgroundColor(configuration.isPressed)) .cornerRadius(Consts.cornerRadius) + .contentShape(Rectangle()) // Makes whole button area tappable, when there's no background + } + + private func foregroundColor(_ isPressed: Bool) -> Color { + switch (colorScheme, isPressed) { + case (.dark, false): + return .blue30 + case (.dark, true): + return .blue20 + case (_, false): + return .blueBase + case (_, true): + return .blue70 + } + } + + private func backgroundColor(_ isPressed: Bool) -> Color { + switch (colorScheme, isPressed) { + case (.light, true): + return .blueBase.opacity(0.2) + case (.dark, true): + return .blue30.opacity(0.2) + default: + return .clear + } } } private enum Consts { - static let cornerRadius: CGFloat = 12 + static let cornerRadius: CGFloat = 8 static let height: CGFloat = 50 static let fontSize: CGFloat = 16 static let pressedOpacity: CGFloat = 0.7 From aad9ecc91b9114fb8ff77881edbad306ebd6577c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Wed, 10 Apr 2024 15:30:43 +0200 Subject: [PATCH 224/245] Release 7.115.0-1 (#2707) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 47f83b0043..8927490201 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8278,7 +8278,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8315,7 +8315,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8407,7 +8407,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8435,7 +8435,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8585,7 +8585,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8611,7 +8611,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8676,7 +8676,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8711,7 +8711,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8745,7 +8745,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8776,7 +8776,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9063,7 +9063,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9094,7 +9094,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9123,7 +9123,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9157,7 +9157,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9188,7 +9188,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9221,11 +9221,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9459,7 +9459,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9486,7 +9486,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9519,7 +9519,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9557,7 +9557,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9593,7 +9593,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9628,11 +9628,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9806,11 +9806,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9839,10 +9839,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 0; + CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 0; + DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 6134ca70d1b0369b6ee93c1aeb97309e11d4ceac Mon Sep 17 00:00:00 2001 From: amddg44 Date: Wed, 10 Apr 2024 16:12:40 +0200 Subject: [PATCH 225/245] Password Manager widget and app shortcut (#2619) Task/Issue URL: https://app.asana.com/0/42792087274227/1206508535741153/f Tech Design URL: CC: Description: Adds home screen widget, lock screen widget & app shortcut to quickly access passwords --- Core/AppDeepLinkSchemes.swift | 1 + Core/PixelEvent.swift | 12 +- DuckDuckGo/AppDelegate+AppDeepLinks.swift | 12 ++ DuckDuckGo/AppDelegate.swift | 13 ++- DuckDuckGo/AutofillLoginListViewModel.swift | 2 + ...ofillLoginSettingsListViewController.swift | 50 ++++++++- DuckDuckGo/Info.plist | 8 ++ DuckDuckGo/MainViewController.swift | 5 +- DuckDuckGo/bg.lproj/InfoPlist.strings | 3 + DuckDuckGo/cs.lproj/InfoPlist.strings | 3 + DuckDuckGo/da.lproj/InfoPlist.strings | 3 + DuckDuckGo/de.lproj/InfoPlist.strings | 3 + DuckDuckGo/el.lproj/InfoPlist.strings | 3 + DuckDuckGo/en.lproj/InfoPlist.strings | 3 + DuckDuckGo/es.lproj/InfoPlist.strings | 3 + DuckDuckGo/et.lproj/InfoPlist.strings | 3 + DuckDuckGo/fi.lproj/InfoPlist.strings | 3 + DuckDuckGo/fr.lproj/InfoPlist.strings | 3 + DuckDuckGo/hr.lproj/InfoPlist.strings | 3 + DuckDuckGo/hu.lproj/InfoPlist.strings | 3 + DuckDuckGo/it.lproj/InfoPlist.strings | 3 + DuckDuckGo/lt.lproj/InfoPlist.strings | 3 + DuckDuckGo/lv.lproj/InfoPlist.strings | 3 + DuckDuckGo/nb.lproj/InfoPlist.strings | 3 + DuckDuckGo/nl.lproj/InfoPlist.strings | 3 + DuckDuckGo/pl.lproj/InfoPlist.strings | 3 + DuckDuckGo/pt.lproj/InfoPlist.strings | 3 + DuckDuckGo/ro.lproj/InfoPlist.strings | 3 + DuckDuckGo/ru.lproj/InfoPlist.strings | 3 + DuckDuckGo/sk.lproj/InfoPlist.strings | 3 + DuckDuckGo/sl.lproj/InfoPlist.strings | 3 + DuckDuckGo/sv.lproj/InfoPlist.strings | 3 + DuckDuckGo/tr.lproj/InfoPlist.strings | 3 + .../AccentColor.colorset/Contents.json | 20 ---- .../Contents.json | 38 ------- .../Contents.json | 38 ------- .../Contents.json | 20 ---- .../Contents.json | 38 ------- .../Contents.json | 12 +- .../Contents.json | 38 ------- .../Contents.json | 3 +- .../LockScreenPasswords.svg | 3 + .../Contents.json | 12 ++ .../WidgetPasswordIllustration.svg | 31 ++++++ Widgets/ColorExtension.swift | 6 - Widgets/DeepLinks.swift | 1 + Widgets/LockScreenWidgets.swift | 16 +++ Widgets/UserText.swift | 23 ++++ Widgets/WidgetViews.swift | 104 ++++++++++++------ Widgets/Widgets.swift | 16 +++ Widgets/bg.lproj/Localizable.strings | 15 +++ Widgets/cs.lproj/Localizable.strings | 15 +++ Widgets/da.lproj/Localizable.strings | 15 +++ Widgets/de.lproj/Localizable.strings | 15 +++ Widgets/el.lproj/Localizable.strings | 15 +++ Widgets/en.lproj/Localizable.strings | 15 +++ Widgets/es.lproj/Localizable.strings | 15 +++ Widgets/et.lproj/Localizable.strings | 15 +++ Widgets/fi.lproj/Localizable.strings | 15 +++ Widgets/fr.lproj/Localizable.strings | 15 +++ Widgets/hr.lproj/Localizable.strings | 15 +++ Widgets/hu.lproj/Localizable.strings | 15 +++ Widgets/it.lproj/Localizable.strings | 15 +++ Widgets/lt.lproj/Localizable.strings | 15 +++ Widgets/lv.lproj/Localizable.strings | 15 +++ Widgets/nb.lproj/Localizable.strings | 15 +++ Widgets/nl.lproj/Localizable.strings | 15 +++ Widgets/pl.lproj/Localizable.strings | 15 +++ Widgets/pt.lproj/Localizable.strings | 15 +++ Widgets/ro.lproj/Localizable.strings | 15 +++ Widgets/ru.lproj/Localizable.strings | 15 +++ Widgets/sk.lproj/Localizable.strings | 15 +++ Widgets/sl.lproj/Localizable.strings | 15 +++ Widgets/sv.lproj/Localizable.strings | 15 +++ Widgets/tr.lproj/Localizable.strings | 15 +++ 75 files changed, 726 insertions(+), 246 deletions(-) delete mode 100644 Widgets/Assets.xcassets/DeprecatedColors/AccentColor.colorset/Contents.json delete mode 100644 Widgets/Assets.xcassets/DeprecatedColors/WidgetAddFavoriteMessageColor.colorset/Contents.json delete mode 100644 Widgets/Assets.xcassets/DeprecatedColors/WidgetBackgroundColor.colorset/Contents.json delete mode 100644 Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoriteLetterColor.colorset/Contents.json delete mode 100644 Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoritesBackgroundColor.colorset/Contents.json delete mode 100644 Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldTextColor.colorset/Contents.json rename Widgets/Assets.xcassets/{DeprecatedColors/WidgetBackground.colorset => LockScreenPasswords.imageset}/Contents.json (63%) create mode 100644 Widgets/Assets.xcassets/LockScreenPasswords.imageset/LockScreenPasswords.svg create mode 100644 Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/Contents.json create mode 100644 Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/WidgetPasswordIllustration.svg diff --git a/Core/AppDeepLinkSchemes.swift b/Core/AppDeepLinkSchemes.swift index 38fa976b99..5230506ba2 100644 --- a/Core/AppDeepLinkSchemes.swift +++ b/Core/AppDeepLinkSchemes.swift @@ -32,6 +32,7 @@ public enum AppDeepLinkSchemes: String, CaseIterable { case addFavorite = "ddgAddFavorite" case openVPN = "ddgOpenVPN" + case openPasswords = "ddgOpenPasswords" public var url: URL { URL(string: rawValue + "://")! diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index c247505293..1e7119aa3c 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -212,7 +212,11 @@ extension Pixel { case autofillLoginsPasswordGenerationPromptDisplayed case autofillLoginsPasswordGenerationPromptConfirmed case autofillLoginsPasswordGenerationPromptDismissed - + + case autofillLoginsLaunchWidgetHome + case autofillLoginsLaunchWidgetLock + case autofillLoginsLaunchAppShortcut + case autofillJSPixelFired(_ pixel: AutofillUserScript.JSPixel) case secureVaultError @@ -839,7 +843,11 @@ extension Pixel.Event { case .autofillLoginsPasswordGenerationPromptDisplayed: return "m_autofill_logins_password_generation_prompt_displayed" case .autofillLoginsPasswordGenerationPromptConfirmed: return "m_autofill_logins_password_generation_prompt_confirmed" case .autofillLoginsPasswordGenerationPromptDismissed: return "m_autofill_logins_password_generation_prompt_dismissed" - + + case .autofillLoginsLaunchWidgetHome: return "m_autofill_logins_launch_widget_home" + case .autofillLoginsLaunchWidgetLock: return "m_autofill_logins_launch_widget_lock" + case .autofillLoginsLaunchAppShortcut: return "m_autofill_logins_launch_app_shortcut" + case .autofillJSPixelFired(let pixel): return "m_ios_\(pixel.pixelName)" diff --git a/DuckDuckGo/AppDelegate+AppDeepLinks.swift b/DuckDuckGo/AppDelegate+AppDeepLinks.swift index 116d7860b8..2021700538 100644 --- a/DuckDuckGo/AppDelegate+AppDeepLinks.swift +++ b/DuckDuckGo/AppDelegate+AppDeepLinks.swift @@ -60,6 +60,18 @@ extension AppDelegate { presentNetworkProtectionStatusSettingsModal() #endif + case .openPasswords: + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { + mainViewController.launchAutofillLogins(openSearch: true) + } + if let components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems, + let lsItem = queryItems.first(where: { $0.name == "ls" }) { + Pixel.fire(pixel: .autofillLoginsLaunchWidgetLock) + } else { + Pixel.fire(pixel: .autofillLoginsLaunchWidgetHome) + } + default: guard app.applicationState == .active, let currentTab = mainViewController.currentTab else { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index abae396b54..a485e5561e 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -54,6 +54,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { private struct ShortcutKey { static let clipboard = "com.duckduckgo.mobile.ios.clipboard" + static let passwords = "com.duckduckgo.mobile.ios.passwords" #if NETWORK_PROTECTION static let openVPNSettings = "com.duckduckgo.mobile.ios.vpn.open-settings" @@ -885,7 +886,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { os_log("Handling shortcut item: %s", log: .generalLog, type: .debug, shortcutItem.type) Task { @MainActor in - + await autoClear?.applicationWillMoveToForeground() if shortcutItem.type == ShortcutKey.clipboard, let query = UIPasteboard.general.string { @@ -894,6 +895,16 @@ class AppDelegate: UIResponder, UIApplicationDelegate { return } + if shortcutItem.type == ShortcutKey.passwords { + mainViewController?.clearNavigationStack() + // Give the `clearNavigationStack` call time to complete. + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { [weak self] in + self?.mainViewController?.launchAutofillLogins(openSearch: true) + } + Pixel.fire(pixel: .autofillLoginsLaunchAppShortcut) + return + } + #if NETWORK_PROTECTION if shortcutItem.type == ShortcutKey.openVPNSettings { let visibility = DefaultNetworkProtectionVisibility() diff --git a/DuckDuckGo/AutofillLoginListViewModel.swift b/DuckDuckGo/AutofillLoginListViewModel.swift index bead3e9829..2b3850f1b5 100644 --- a/DuckDuckGo/AutofillLoginListViewModel.swift +++ b/DuckDuckGo/AutofillLoginListViewModel.swift @@ -70,6 +70,8 @@ final class AutofillLoginListViewModel: ObservableObject { } } var authenticationNotRequired = false + var isCancelingSearch = false + @Published private var accounts = [SecureVaultModels.WebsiteAccount]() private var accountsToSuggest = [SecureVaultModels.WebsiteAccount]() private var cancellables: Set = [] diff --git a/DuckDuckGo/AutofillLoginSettingsListViewController.swift b/DuckDuckGo/AutofillLoginSettingsListViewController.swift index afd8236e5b..ca339b7b8c 100644 --- a/DuckDuckGo/AutofillLoginSettingsListViewController.swift +++ b/DuckDuckGo/AutofillLoginSettingsListViewController.swift @@ -87,6 +87,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { private lazy var searchController: UISearchController = { let searchController = UISearchController(searchResultsController: nil) searchController.searchResultsUpdater = self + searchController.searchBar.delegate = self searchController.obscuresBackgroundDuringPresentation = false searchController.searchBar.placeholder = UserText.autofillLoginListSearchPlaceholder navigationItem.hidesSearchBarWhenScrolling = false @@ -129,8 +130,14 @@ final class AutofillLoginSettingsListViewController: UIViewController { }() var selectedAccount: SecureVaultModels.WebsiteAccount? - - init(appSettings: AppSettings, currentTabUrl: URL? = nil, syncService: DDGSyncing, syncDataProviders: SyncDataProviders, selectedAccount: SecureVaultModels.WebsiteAccount?) { + var openSearch: Bool + + init(appSettings: AppSettings, + currentTabUrl: URL? = nil, + syncService: DDGSyncing, + syncDataProviders: SyncDataProviders, + selectedAccount: SecureVaultModels.WebsiteAccount?, + openSearch: Bool = false) { let secureVault = try? AutofillSecureVaultFactory.makeVault(errorReporter: SecureVaultErrorReporter.shared) if secureVault == nil { os_log("Failed to make vault") @@ -138,6 +145,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { self.viewModel = AutofillLoginListViewModel(appSettings: appSettings, tld: tld, secureVault: secureVault, currentTabUrl: currentTabUrl) self.syncService = syncService self.selectedAccount = selectedAccount + self.openSearch = openSearch super.init(nibName: nil, bundle: nil) syncUpdatesCancellable = syncDataProviders.credentialsAdapter.syncDidCompletePublisher @@ -172,11 +180,10 @@ final class AutofillLoginSettingsListViewController: UIViewController { registerForKeyboardNotifications() } - + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) authenticate() - } override func viewWillDisappear(_ animated: Bool) { @@ -337,6 +344,7 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } else { showSelectedAccountIfRequired() + openSearchIfRequired() self.syncService.scheduler.requestSyncImmediately() } } @@ -349,6 +357,18 @@ final class AutofillLoginSettingsListViewController: UIViewController { } } + private func openSearchIfRequired() { + // Don't auto open search if user has selected an account + guard selectedAccount == nil else { return } + + if openSearch { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + self.searchController.searchBar.searchTextField.becomeFirstResponder() + } + openSearch = false + } + } + private func presentDeleteConfirmation(for title: String, domain: String) { let message = title.isEmpty ? UserText.autofillLoginListLoginDeletedToastMessageNoTitle : UserText.autofillLoginListLoginDeletedToastMessage(for: title) @@ -901,12 +921,32 @@ extension AutofillLoginSettingsListViewController: UISearchResultsUpdating { func updateSearchResults(for searchController: UISearchController) { viewModel.isSearching = searchController.isActive - if let query = searchController.searchBar.text { + + if viewModel.isSearching { + viewModel.isCancelingSearch = false + } + + if !viewModel.isCancelingSearch, let query = searchController.searchBar.text { viewModel.filterData(with: query) emptySearchView.query = query tableView.reloadData() } } + + func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + searchController.searchBar.resignFirstResponder() + } +} + +extension AutofillLoginSettingsListViewController: UISearchBarDelegate { + + func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { + viewModel.isCancelingSearch = true + viewModel.isSearching = false + + viewModel.filterData(with: "") + tableView.reloadData() + } } // MARK: Keyboard diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 86ef7b8d64..f37560b6dc 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -199,6 +199,14 @@ UIApplicationShortcutItemType com.duckduckgo.mobile.ios.clipboard + + UIApplicationShortcutItemIconFile + Key-24 + UIApplicationShortcutItemTitle + Search Passwords + UIApplicationShortcutItemType + com.duckduckgo.mobile.ios.passwords + UIBackgroundModes diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index d4d92afa17..49b978243b 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1175,14 +1175,15 @@ class MainViewController: UIViewController { suggestionTrayController?.didHide() } - func launchAutofillLogins(with currentTabUrl: URL? = nil) { + func launchAutofillLogins(with currentTabUrl: URL? = nil, openSearch: Bool = false) { let appSettings = AppDependencyProvider.shared.appSettings let autofillSettingsViewController = AutofillLoginSettingsListViewController( appSettings: appSettings, currentTabUrl: currentTabUrl, syncService: syncService, syncDataProviders: syncDataProviders, - selectedAccount: nil + selectedAccount: nil, + openSearch: openSearch ) autofillSettingsViewController.delegate = self let navigationController = UINavigationController(rootViewController: autofillSettingsViewController) diff --git a/DuckDuckGo/bg.lproj/InfoPlist.strings b/DuckDuckGo/bg.lproj/InfoPlist.strings index 391f243270..5b02279c0d 100644 --- a/DuckDuckGo/bg.lproj/InfoPlist.strings +++ b/DuckDuckGo/bg.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Поставяне от клипборда"; +/* (No Comment) */ +"Search Passwords" = "Търсене на пароли"; + diff --git a/DuckDuckGo/cs.lproj/InfoPlist.strings b/DuckDuckGo/cs.lproj/InfoPlist.strings index 1d1f7ef929..24f4c51c64 100644 --- a/DuckDuckGo/cs.lproj/InfoPlist.strings +++ b/DuckDuckGo/cs.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Vložit ze schránky"; +/* (No Comment) */ +"Search Passwords" = "Prohledat hesla"; + diff --git a/DuckDuckGo/da.lproj/InfoPlist.strings b/DuckDuckGo/da.lproj/InfoPlist.strings index c217ef1a24..c06b3b2c92 100644 --- a/DuckDuckGo/da.lproj/InfoPlist.strings +++ b/DuckDuckGo/da.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Indsæt fra udklipsholder"; +/* (No Comment) */ +"Search Passwords" = "Søg adgangskoder"; + diff --git a/DuckDuckGo/de.lproj/InfoPlist.strings b/DuckDuckGo/de.lproj/InfoPlist.strings index 07c21d1518..4c99262b18 100644 --- a/DuckDuckGo/de.lproj/InfoPlist.strings +++ b/DuckDuckGo/de.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Vom Clipboard einfügen"; +/* (No Comment) */ +"Search Passwords" = "Passwörter suchen"; + diff --git a/DuckDuckGo/el.lproj/InfoPlist.strings b/DuckDuckGo/el.lproj/InfoPlist.strings index c9fa2d99eb..f806a16598 100644 --- a/DuckDuckGo/el.lproj/InfoPlist.strings +++ b/DuckDuckGo/el.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Επικόλληση από το πρόχειρο"; +/* (No Comment) */ +"Search Passwords" = "Αναζήτηση κωδικών πρόσβασης"; + diff --git a/DuckDuckGo/en.lproj/InfoPlist.strings b/DuckDuckGo/en.lproj/InfoPlist.strings index 39fbc7746d..b8852b959f 100644 --- a/DuckDuckGo/en.lproj/InfoPlist.strings +++ b/DuckDuckGo/en.lproj/InfoPlist.strings @@ -22,3 +22,6 @@ /* (No Comment) */ "Paste from clipboard" = "Paste from clipboard"; +/* (No Comment) */ +"Search Passwords" = "Search Passwords"; + diff --git a/DuckDuckGo/es.lproj/InfoPlist.strings b/DuckDuckGo/es.lproj/InfoPlist.strings index 4fe6c79c19..28ff258807 100644 --- a/DuckDuckGo/es.lproj/InfoPlist.strings +++ b/DuckDuckGo/es.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Pegar desde el portapapeles"; +/* (No Comment) */ +"Search Passwords" = "Buscar contraseñas"; + diff --git a/DuckDuckGo/et.lproj/InfoPlist.strings b/DuckDuckGo/et.lproj/InfoPlist.strings index 1b190642ae..4a6dd0b793 100644 --- a/DuckDuckGo/et.lproj/InfoPlist.strings +++ b/DuckDuckGo/et.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Kleebi lõikelaualt"; +/* (No Comment) */ +"Search Passwords" = "Otsi paroole"; + diff --git a/DuckDuckGo/fi.lproj/InfoPlist.strings b/DuckDuckGo/fi.lproj/InfoPlist.strings index 3f902781b9..a4d567be80 100644 --- a/DuckDuckGo/fi.lproj/InfoPlist.strings +++ b/DuckDuckGo/fi.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Liitä leikepöydältä"; +/* (No Comment) */ +"Search Passwords" = "Etsi salasanoja"; + diff --git a/DuckDuckGo/fr.lproj/InfoPlist.strings b/DuckDuckGo/fr.lproj/InfoPlist.strings index 3da4495d8d..3a9ec0c05f 100644 --- a/DuckDuckGo/fr.lproj/InfoPlist.strings +++ b/DuckDuckGo/fr.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Coller depuis le presse-papier"; +/* (No Comment) */ +"Search Passwords" = "Rechercher un mot de passe"; + diff --git a/DuckDuckGo/hr.lproj/InfoPlist.strings b/DuckDuckGo/hr.lproj/InfoPlist.strings index c56cf3fbf2..aff121440c 100644 --- a/DuckDuckGo/hr.lproj/InfoPlist.strings +++ b/DuckDuckGo/hr.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Zalijepi iz međuspremnika"; +/* (No Comment) */ +"Search Passwords" = "Pretraživanje lozinki"; + diff --git a/DuckDuckGo/hu.lproj/InfoPlist.strings b/DuckDuckGo/hu.lproj/InfoPlist.strings index 0ce5f4b56b..f911ffb5c8 100644 --- a/DuckDuckGo/hu.lproj/InfoPlist.strings +++ b/DuckDuckGo/hu.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Beillesztés a vágólapról"; +/* (No Comment) */ +"Search Passwords" = "Jelszavak keresése"; + diff --git a/DuckDuckGo/it.lproj/InfoPlist.strings b/DuckDuckGo/it.lproj/InfoPlist.strings index fd6838438d..ab17e18030 100644 --- a/DuckDuckGo/it.lproj/InfoPlist.strings +++ b/DuckDuckGo/it.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Incolla dagli appunti"; +/* (No Comment) */ +"Search Passwords" = "Cerca password"; + diff --git a/DuckDuckGo/lt.lproj/InfoPlist.strings b/DuckDuckGo/lt.lproj/InfoPlist.strings index 9aeec65e57..99fd8bb786 100644 --- a/DuckDuckGo/lt.lproj/InfoPlist.strings +++ b/DuckDuckGo/lt.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Įklijuoti iš mainų srities"; +/* (No Comment) */ +"Search Passwords" = "Ieškoti slaptažodžių"; + diff --git a/DuckDuckGo/lv.lproj/InfoPlist.strings b/DuckDuckGo/lv.lproj/InfoPlist.strings index 7aae868fb5..5566719173 100644 --- a/DuckDuckGo/lv.lproj/InfoPlist.strings +++ b/DuckDuckGo/lv.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Ielīmēt no starpliktuves"; +/* (No Comment) */ +"Search Passwords" = "Meklēt paroles"; + diff --git a/DuckDuckGo/nb.lproj/InfoPlist.strings b/DuckDuckGo/nb.lproj/InfoPlist.strings index a218989952..94b5181052 100644 --- a/DuckDuckGo/nb.lproj/InfoPlist.strings +++ b/DuckDuckGo/nb.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Lim inn fra utklippstavlen"; +/* (No Comment) */ +"Search Passwords" = "Søk i passord"; + diff --git a/DuckDuckGo/nl.lproj/InfoPlist.strings b/DuckDuckGo/nl.lproj/InfoPlist.strings index 51c0610f15..762c61953c 100644 --- a/DuckDuckGo/nl.lproj/InfoPlist.strings +++ b/DuckDuckGo/nl.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Plakken vanaf klembord"; +/* (No Comment) */ +"Search Passwords" = "Wachtwoorden zoeken"; + diff --git a/DuckDuckGo/pl.lproj/InfoPlist.strings b/DuckDuckGo/pl.lproj/InfoPlist.strings index 8918f5fef6..e75ef93175 100644 --- a/DuckDuckGo/pl.lproj/InfoPlist.strings +++ b/DuckDuckGo/pl.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Wklej ze schowka"; +/* (No Comment) */ +"Search Passwords" = "Wyszukaj hasła"; + diff --git a/DuckDuckGo/pt.lproj/InfoPlist.strings b/DuckDuckGo/pt.lproj/InfoPlist.strings index a0e70cc845..64b344153a 100644 --- a/DuckDuckGo/pt.lproj/InfoPlist.strings +++ b/DuckDuckGo/pt.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Colar da área de transferência"; +/* (No Comment) */ +"Search Passwords" = "Pesquisar palavras-passe"; + diff --git a/DuckDuckGo/ro.lproj/InfoPlist.strings b/DuckDuckGo/ro.lproj/InfoPlist.strings index 5d24ab9cea..bc5c1c58e5 100644 --- a/DuckDuckGo/ro.lproj/InfoPlist.strings +++ b/DuckDuckGo/ro.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Lipește din clipboard"; +/* (No Comment) */ +"Search Passwords" = "Caută parole"; + diff --git a/DuckDuckGo/ru.lproj/InfoPlist.strings b/DuckDuckGo/ru.lproj/InfoPlist.strings index b74dd8ed64..44db5ece43 100644 --- a/DuckDuckGo/ru.lproj/InfoPlist.strings +++ b/DuckDuckGo/ru.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Вставить из буфера обмена"; +/* (No Comment) */ +"Search Passwords" = "Найти пароль"; + diff --git a/DuckDuckGo/sk.lproj/InfoPlist.strings b/DuckDuckGo/sk.lproj/InfoPlist.strings index 83b51c66f2..93a7ee1bfb 100644 --- a/DuckDuckGo/sk.lproj/InfoPlist.strings +++ b/DuckDuckGo/sk.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Prilepiť zo schránky"; +/* (No Comment) */ +"Search Passwords" = "Vyhľadávanie hesiel"; + diff --git a/DuckDuckGo/sl.lproj/InfoPlist.strings b/DuckDuckGo/sl.lproj/InfoPlist.strings index d8cef311b8..edb9e7867e 100644 --- a/DuckDuckGo/sl.lproj/InfoPlist.strings +++ b/DuckDuckGo/sl.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Prilepi iz odložišča"; +/* (No Comment) */ +"Search Passwords" = "Iskanje gesel"; + diff --git a/DuckDuckGo/sv.lproj/InfoPlist.strings b/DuckDuckGo/sv.lproj/InfoPlist.strings index af2fad8120..2a48666232 100644 --- a/DuckDuckGo/sv.lproj/InfoPlist.strings +++ b/DuckDuckGo/sv.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Klistra in från urklipp"; +/* (No Comment) */ +"Search Passwords" = "Sök lösenord"; + diff --git a/DuckDuckGo/tr.lproj/InfoPlist.strings b/DuckDuckGo/tr.lproj/InfoPlist.strings index b1e4689d00..35ef498cdc 100644 --- a/DuckDuckGo/tr.lproj/InfoPlist.strings +++ b/DuckDuckGo/tr.lproj/InfoPlist.strings @@ -28,3 +28,6 @@ /* (No Comment) */ "Paste from Clipboard" = "Panodan yapıştır"; +/* (No Comment) */ +"Search Passwords" = "Şifreleri ara"; + diff --git a/Widgets/Assets.xcassets/DeprecatedColors/AccentColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/AccentColor.colorset/Contents.json deleted file mode 100644 index de95d5b40b..0000000000 --- a/Widgets/Assets.xcassets/DeprecatedColors/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "0.561", - "red" : "0.404" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetAddFavoriteMessageColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/WidgetAddFavoriteMessageColor.colorset/Contents.json deleted file mode 100644 index 15bc03d1e6..0000000000 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetAddFavoriteMessageColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.533", - "green" : "0.533", - "red" : "0.533" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.800", - "green" : "0.800", - "red" : "0.800" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetBackgroundColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/WidgetBackgroundColor.colorset/Contents.json deleted file mode 100644 index 35bed5da10..0000000000 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetBackgroundColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "display-p3", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "display-p3", - "components" : { - "alpha" : "1.000", - "blue" : "0.067", - "green" : "0.067", - "red" : "0.067" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoriteLetterColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoriteLetterColor.colorset/Contents.json deleted file mode 100644 index 97650a1a6e..0000000000 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoriteLetterColor.colorset/Contents.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoritesBackgroundColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoritesBackgroundColor.colorset/Contents.json deleted file mode 100644 index 5ae8b4dfdb..0000000000 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetFavoritesBackgroundColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.980", - "green" : "0.980", - "red" : "0.980" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.133", - "green" : "0.133", - "red" : "0.133" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldBackgroundColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldBackgroundColor.colorset/Contents.json index 33cbb418f9..f47c10dd49 100644 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldBackgroundColor.colorset/Contents.json +++ b/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldBackgroundColor.colorset/Contents.json @@ -5,9 +5,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.918", - "green" : "0.918", - "red" : "0.918" + "blue" : "0xEA", + "green" : "0xEA", + "red" : "0xEA" } }, "idiom" : "universal" @@ -23,9 +23,9 @@ "color-space" : "srgb", "components" : { "alpha" : "1.000", - "blue" : "0.200", - "green" : "0.200", - "red" : "0.200" + "blue" : "0x44", + "green" : "0x44", + "red" : "0x44" } }, "idiom" : "universal" diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldTextColor.colorset/Contents.json b/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldTextColor.colorset/Contents.json deleted file mode 100644 index b9f68fe621..0000000000 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetSearchFieldTextColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "colors" : [ - { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "0.533", - "green" : "0.533", - "red" : "0.533" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/Widgets/Assets.xcassets/DeprecatedColors/WidgetBackground.colorset/Contents.json b/Widgets/Assets.xcassets/LockScreenPasswords.imageset/Contents.json similarity index 63% rename from Widgets/Assets.xcassets/DeprecatedColors/WidgetBackground.colorset/Contents.json rename to Widgets/Assets.xcassets/LockScreenPasswords.imageset/Contents.json index eb87897008..75f67d368d 100644 --- a/Widgets/Assets.xcassets/DeprecatedColors/WidgetBackground.colorset/Contents.json +++ b/Widgets/Assets.xcassets/LockScreenPasswords.imageset/Contents.json @@ -1,6 +1,7 @@ { - "colors" : [ + "images" : [ { + "filename" : "LockScreenPasswords.svg", "idiom" : "universal" } ], diff --git a/Widgets/Assets.xcassets/LockScreenPasswords.imageset/LockScreenPasswords.svg b/Widgets/Assets.xcassets/LockScreenPasswords.imageset/LockScreenPasswords.svg new file mode 100644 index 0000000000..e575e2c06b --- /dev/null +++ b/Widgets/Assets.xcassets/LockScreenPasswords.imageset/LockScreenPasswords.svg @@ -0,0 +1,3 @@ + + + diff --git a/Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/Contents.json b/Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/Contents.json new file mode 100644 index 0000000000..21b36edd1d --- /dev/null +++ b/Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "WidgetPasswordIllustration.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/WidgetPasswordIllustration.svg b/Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/WidgetPasswordIllustration.svg new file mode 100644 index 0000000000..43a77a35e6 --- /dev/null +++ b/Widgets/Assets.xcassets/WidgetPasswordIllustration.imageset/WidgetPasswordIllustration.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Widgets/ColorExtension.swift b/Widgets/ColorExtension.swift index c464779c57..6496260e62 100644 --- a/Widgets/ColorExtension.swift +++ b/Widgets/ColorExtension.swift @@ -21,13 +21,7 @@ import SwiftUI extension Color { - static let widgetBackground = Color("WidgetBackgroundColor") - static let widgetFavoritesBackground = Color("WidgetFavoritesBackgroundColor") static let widgetSearchFieldBackground = Color("WidgetSearchFieldBackgroundColor") - static let widgetSearchFieldText = Color("WidgetSearchFieldTextColor") - static let widgetFavoriteLetter = Color("WidgetFavoriteLetterColor") - static let widgetAddFavoriteCTA = Color(.cornflowerBlue) - static let widgetAddFavoriteMessage = Color("WidgetAddFavoriteMessageColor") static func forDomain(_ domain: String) -> Color { return Color(UIColor.forDomain(domain)) diff --git a/Widgets/DeepLinks.swift b/Widgets/DeepLinks.swift index 0bdf850738..dd300973ea 100644 --- a/Widgets/DeepLinks.swift +++ b/Widgets/DeepLinks.swift @@ -30,4 +30,5 @@ struct DeepLinks { static let addFavorite = AppDeepLinkSchemes.addFavorite.url static let openVPN = AppDeepLinkSchemes.openVPN.url + static let openPasswords = AppDeepLinkSchemes.openPasswords.url } diff --git a/Widgets/LockScreenWidgets.swift b/Widgets/LockScreenWidgets.swift index b0d0376449..b1d9c1ae0f 100644 --- a/Widgets/LockScreenWidgets.swift +++ b/Widgets/LockScreenWidgets.swift @@ -103,6 +103,22 @@ struct FireButtonLockScreenWidget: Widget { } } +@available(iOSApplicationExtension 16.0, *) +struct PasswordsLockScreenWidget: Widget { + + let kind: String = "PasswordsLockScreenWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { _ in + return LockScreenWidgetView(imageNamed: "LockScreenPasswords") + .widgetURL(DeepLinks.openPasswords.appendingParameter(name: "ls", value: "1")) + } + .configurationDisplayName(UserText.lockScreenPasswordsTitle) + .description(UserText.lockScreenPasswordsDescription) + .supportedFamilies([ .accessoryCircular ]) + } +} + struct LockScreenWidgetView: View { let imageNamed: String diff --git a/Widgets/UserText.swift b/Widgets/UserText.swift index 1824a668ba..693d562c02 100644 --- a/Widgets/UserText.swift +++ b/Widgets/UserText.swift @@ -49,6 +49,18 @@ struct UserText { value: "Add Favorites", comment: "CTA shown in the favorites widget empty state.") + static let passwordsWidgetGalleryDisplayName = NSLocalizedString("widget.gallery.passwords.display.name", + value: "Search Passwords", + comment: "Display name for search passwords widget in widget gallery") + + static let passwordsWidgetGalleryDescription = NSLocalizedString("widget.gallery.passwords.description", + value: "Quickly search your saved DuckDuckGo passwords.", + comment: "Description of search passwords widget in widget gallery") + + static let passwords = NSLocalizedString("widget.passwords", + value: "Search Passwords", + comment: "Text in passwords widget") + static let lockScreenSearchTitle = NSLocalizedString( "lock.screen.widget.search.title", value: "Private Search", @@ -98,4 +110,15 @@ struct UserText { "lock.screen.widget.fire.description", value: "Instantly delete your browsing history and start a new private search in DuckDuckGo.", comment: "Description shown to the user when adding the Fire Button lock screen widget") + + static let lockScreenPasswordsTitle = NSLocalizedString( + "lock.screen.widget.passwords.title", + value: "Search Passwords", + comment: "Title shown to the user when adding the Search Passwords lock screen widget") + + static let lockScreenPasswordsDescription = NSLocalizedString( + "lock.screen.widget.passwords.description", + value: "Quickly search your saved DuckDuckGo passwords.", + comment: "Description shown to the user when adding the Search Passwords lock screen widget") + } diff --git a/Widgets/WidgetViews.swift b/Widgets/WidgetViews.swift index 7991d20620..b03d040cd2 100644 --- a/Widgets/WidgetViews.swift +++ b/Widgets/WidgetViews.swift @@ -19,7 +19,9 @@ import SwiftUI import WidgetKit +import DesignResourcesKit +// swiftlint:disable file_length struct FavoriteView: View { var favorite: Favorite? @@ -28,8 +30,8 @@ struct FavoriteView: View { var body: some View { ZStack { - RoundedRectangle(cornerRadius: 10) - .fill(Color.widgetFavoritesBackground) + RoundedRectangle(cornerRadius: 8) + .fill(Color(designSystemColor: .container)) if let favorite = favorite { @@ -37,8 +39,8 @@ struct FavoriteView: View { ZStack { - RoundedRectangle(cornerRadius: 10) - .fill(favorite.needsColorBackground ? Color.forDomain(favorite.domain) : Color.widgetFavoritesBackground) + RoundedRectangle(cornerRadius: 8) + .fill(favorite.needsColorBackground ? Color.forDomain(favorite.domain) : Color(designSystemColor: .container)) .shadow(color: Color.black.opacity(0.08), radius: 8, x: 0, y: 2) if let image = favorite.favicon { @@ -49,14 +51,14 @@ struct FavoriteView: View { } else if favorite.isDuckDuckGo { - Image("WidgetDaxLogo") + Image(.duckDuckGoColor24) .resizable() .frame(width: 45, height: 45, alignment: .center) } else { Text(favorite.domain.first?.uppercased() ?? "") - .foregroundColor(Color.widgetFavoriteLetter) + .foregroundColor(Color.white) .font(.system(size: 42)) } @@ -81,25 +83,26 @@ struct LargeSearchFieldView: View { Link(destination: DeepLinks.newSearch) { ZStack { - Capsule(style: .circular) + RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) .fill(Color.widgetSearchFieldBackground) .frame(minHeight: 46, maxHeight: 46) - .padding(16) + .padding(.vertical, 16) HStack { - Image("WidgetDaxLogo") - .resizable() + Image(.duckDuckGoColor24) .frame(width: 24, height: 24, alignment: .leading) Text(UserText.searchDuckDuckGo) - .foregroundColor(Color.widgetSearchFieldText) + .daxBodyRegular() + .foregroundColor(Color(designSystemColor: .textSecondary)) Spacer() - Image("WidgetSearchLoupe") + Image(.findSearch20) + .foregroundColor(Color(designSystemColor: .textPrimary).opacity(0.5)) - }.padding(EdgeInsets(top: 0, leading: 27, bottom: 0, trailing: 27)) + }.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16)) }.unredacted() } @@ -124,7 +127,6 @@ struct FavoritesRowView: View { } } - .padding(.horizontal, 16) } @@ -166,7 +168,7 @@ struct FavoritesWidgetView: View { var body: some View { ZStack { - Rectangle().fill(Color.widgetBackground) + Rectangle().fill(Color(designSystemColor: .surface)) VStack(alignment: .center, spacing: 0) { @@ -182,23 +184,22 @@ struct FavoritesWidgetView: View { }.padding(.bottom, 8) - VStack(spacing: 8) { + VStack(spacing: 4) { Text(UserText.noFavoritesMessage) - .font(.system(size: 15)) + .daxSubheadRegular() .multilineTextAlignment(.center) - .foregroundColor(.widgetAddFavoriteMessage) + .foregroundColor(Color(designSystemColor: .textSecondary)) .padding(.horizontal) .accessibilityHidden(true) HStack { Text(UserText.noFavoritesCTA) - .bold() - .font(.system(size: 15)) - .foregroundColor(.widgetAddFavoriteCTA) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .accent)) Image(systemName: "chevron.right") .imageScale(.medium) - .foregroundColor(.widgetAddFavoriteCTA) + .foregroundColor(Color(designSystemColor: .accent)) }.accessibilityHidden(true) } @@ -206,7 +207,7 @@ struct FavoritesWidgetView: View { .padding(EdgeInsets(top: widgetFamily == .systemLarge ? 48 : 60, leading: 0, bottom: 0, trailing: 0)) } - .widgetContainerBackground(color: .widgetBackground) + .widgetContainerBackground(color: Color(designSystemColor: .surface)) } } @@ -216,12 +217,12 @@ struct SearchWidgetView: View { var body: some View { ZStack { Rectangle() - .fill(Color.widgetBackground) + .fill(Color(designSystemColor: .surface)) .accessibilityLabel(Text(UserText.searchDuckDuckGo)) VStack(alignment: .center, spacing: 15) { - Image("WidgetDaxLogo") + Image(.logo) .resizable() .frame(width: 46, height: 46, alignment: .center) .isHidden(false) @@ -229,18 +230,50 @@ struct SearchWidgetView: View { ZStack(alignment: Alignment(horizontal: .trailing, vertical: .center)) { - Capsule(style: .circular) + RoundedRectangle(cornerSize: CGSize(width: 8, height: 8)) .fill(Color.widgetSearchFieldBackground) - .frame(width: 123, height: 46) + .frame(width: 126, height: 46) - Image("WidgetSearchLoupe") - .padding(.trailing) + Image(.findSearch20) + .frame(width: 20, height: 20) + .padding(.leading) + .padding(.trailing, 13) .isHidden(false) - + .accessibilityHidden(true) + .foregroundColor(Color(designSystemColor: .textPrimary).opacity(0.5)) } }.accessibilityHidden(true) } - .widgetContainerBackground(color: .widgetBackground) + .widgetContainerBackground(color: Color(designSystemColor: .surface)) + } +} + +struct PasswordsWidgetView: View { + var entry: Provider.Entry + + var body: some View { + ZStack { + Rectangle() + .fill(Color(designSystemColor: .surface)) + .accessibilityLabel(Text(UserText.passwords)) + + VStack(alignment: .center, spacing: 6) { + + Image(.widgetPasswordIllustration) + .frame(width: 96, height: 72) + .isHidden(false) + .accessibilityHidden(true) + + Text(UserText.passwords) + .daxSubheadRegular() + .foregroundColor(Color(designSystemColor: .textPrimary)) + .multilineTextAlignment(.center) + .padding(.horizontal, 8) + + } + .accessibilityHidden(true) + } + .widgetContainerBackground(color: Color(designSystemColor: .surface)) } } @@ -332,6 +365,14 @@ struct WidgetViews_Previews: PreviewProvider { .previewContext(WidgetPreviewContext(family: .systemSmall)) .environment(\.colorScheme, .dark) + PasswordsWidgetView(entry: emptyState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .light) + + PasswordsWidgetView(entry: emptyState) + .previewContext(WidgetPreviewContext(family: .systemSmall)) + .environment(\.colorScheme, .dark) + // Medium size: FavoritesWidgetView(entry: previewWithFavorites) @@ -369,3 +410,4 @@ struct WidgetViews_Previews: PreviewProvider { .environment(\.colorScheme, .dark) } } +// swiftlint:enable file_length diff --git a/Widgets/Widgets.swift b/Widgets/Widgets.swift index 39ab7b0057..d6fdedd32f 100644 --- a/Widgets/Widgets.swift +++ b/Widgets/Widgets.swift @@ -195,12 +195,27 @@ struct FavoritesWidget: Widget { } } +struct PasswordsWidget: Widget { + let kind: String = "PasswordsWidget" + + var body: some WidgetConfiguration { + StaticConfiguration(kind: kind, provider: Provider()) { entry in + PasswordsWidgetView(entry: entry).widgetURL(DeepLinks.openPasswords) + } + .configurationDisplayName(UserText.passwordsWidgetGalleryDisplayName) + .description(UserText.passwordsWidgetGalleryDescription) + .supportedFamilies([.systemSmall]) + } + +} + @main struct Widgets: WidgetBundle { @WidgetBundleBuilder var body: some Widget { SearchWidget() + PasswordsWidget() FavoritesWidget() #if ALPHA @@ -215,6 +230,7 @@ struct Widgets: WidgetBundle { EmailProtectionLockScreenWidget() FireButtonLockScreenWidget() FavoritesLockScreenWidget() + PasswordsLockScreenWidget() } } diff --git a/Widgets/bg.lproj/Localizable.strings b/Widgets/bg.lproj/Localizable.strings index e36bdab9db..5482eda3fa 100644 --- a/Widgets/bg.lproj/Localizable.strings +++ b/Widgets/bg.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Бързо търсене на запазените пароли за DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Търсене на пароли"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Незабавно стартиране на поверително търсене в DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Бързо търсене на запазените пароли за DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Търсене на пароли"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Насладете се на поверително търсене и сърфиране в любимите си сайтове само с едно докосване."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Отварянето на любимите ви сайтове става бързо."; +/* Text in passwords widget */ +"widget.passwords" = "Търсене на пароли"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Търси с DuckDuckGo"; diff --git a/Widgets/cs.lproj/Localizable.strings b/Widgets/cs.lproj/Localizable.strings index 845cf681a1..cc31a7a6ff 100644 --- a/Widgets/cs.lproj/Localizable.strings +++ b/Widgets/cs.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Rychle prohledej svá hesla uložená na DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Prohledat hesla"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Okamžitě spustí soukromé vyhledávání v DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Rychle hledej svá hesla uložená v DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Prohledat hesla"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Vyhledávejte nebo navštěvujte své oblíbené weby soukromě jediným klepnutím."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Rychle navštivte své oblíbené stránky."; +/* Text in passwords widget */ +"widget.passwords" = "Prohledat hesla"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Vyhledat prostřednictvím DuckDuckGo"; diff --git a/Widgets/da.lproj/Localizable.strings b/Widgets/da.lproj/Localizable.strings index ad5843944c..897007f1ee 100644 --- a/Widgets/da.lproj/Localizable.strings +++ b/Widgets/da.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Søg hurtigt i dine gemte DuckDuckGo-adgangskoder."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Søg adgangskoder"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Start øjeblikkeligt en privat søgning i DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Søg hurtigt i dine gemte DuckDuckGo-adgangskoder."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Søg adgangskoder"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Søg eller besøg dine yndlingssider privat med kun ét tryk."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Besøg hurtigt dine foretrukne websteder."; +/* Text in passwords widget */ +"widget.passwords" = "Søg adgangskoder"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Søg med DuckDuckGo"; diff --git a/Widgets/de.lproj/Localizable.strings b/Widgets/de.lproj/Localizable.strings index 4e8d0d75e5..eac25b6188 100644 --- a/Widgets/de.lproj/Localizable.strings +++ b/Widgets/de.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Durchsuche schnell deine gespeicherten DuckDuckGo-Passwörter."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Passwörter suchen"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Starte sofort eine private Suche in DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Durchsuche schnell deine gespeicherten DuckDuckGo-Passwörter."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Passwörter suchen"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Du kannst deine Lieblingswebsites privat durchsuchen und anzeigen – einfach durch Antippen!"; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Wechsel mit nur einem Klick zu deinen Lieblingswebsites."; +/* Text in passwords widget */ +"widget.passwords" = "Passwörter suchen"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Mit DuckDuckGo suchen"; diff --git a/Widgets/el.lproj/Localizable.strings b/Widgets/el.lproj/Localizable.strings index 6aae28ca9a..444b86daf7 100644 --- a/Widgets/el.lproj/Localizable.strings +++ b/Widgets/el.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Αναζητήστε γρήγορα τους αποθηκευμένους κωδικούς πρόσβασής σας στο DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Αναζήτηση κωδικών πρόσβασης"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Αρχίστε αμέσως μια ιδιωτική αναζήτηση στο DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Αναζητήστε γρήγορα τους αποθηκευμένους κωδικούς πρόσβασής σας στο DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Αναζήτηση κωδικών πρόσβασης"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Κάντε αναζήτηση ή επισκεφτείτε τους αγαπημένους ιστότοπούς σας ιδιωτικά, με μόνο ένα πάτημα."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Επισκεφθείτε γρήγορα τους αγαπημένους σας ιστότοπους."; +/* Text in passwords widget */ +"widget.passwords" = "Αναζήτηση κωδικών πρόσβασης"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Αναζήτηση DuckDuckGo"; diff --git a/Widgets/en.lproj/Localizable.strings b/Widgets/en.lproj/Localizable.strings index 31e12dbf16..55b28f9cd0 100644 --- a/Widgets/en.lproj/Localizable.strings +++ b/Widgets/en.lproj/Localizable.strings @@ -16,6 +16,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Quickly search your saved DuckDuckGo passwords."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Search Passwords"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Instantly start a private search in DuckDuckGo."; @@ -28,6 +34,12 @@ /* Title shown to the user when adding the Voice Search lock screen widget */ "lock.screen.widget.voice.title" = "Voice Search"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Quickly search your saved DuckDuckGo passwords."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Search Passwords"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Search or visit your favorite sites privately with just one tap."; @@ -46,6 +58,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Quickly visit your favorite sites."; +/* Text in passwords widget */ +"widget.passwords" = "Search Passwords"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Search DuckDuckGo"; diff --git a/Widgets/es.lproj/Localizable.strings b/Widgets/es.lproj/Localizable.strings index 29ba8d8698..f14ce6328d 100644 --- a/Widgets/es.lproj/Localizable.strings +++ b/Widgets/es.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Busca rápidamente tus contraseñas de DuckDuckGo guardadas."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Buscar contraseñas"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Inicia al instante una búsqueda privada en DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Busca rápidamente tus contraseñas de DuckDuckGo guardadas."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Buscar contraseñas"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Busca o visita tus sitios web de forma privada con solo pulsar un botón."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Visita rápidamente tus sitios favoritos."; +/* Text in passwords widget */ +"widget.passwords" = "Buscar contraseñas"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Buscar en DuckDuckGo"; diff --git a/Widgets/et.lproj/Localizable.strings b/Widgets/et.lproj/Localizable.strings index 87539eae30..adac40a019 100644 --- a/Widgets/et.lproj/Localizable.strings +++ b/Widgets/et.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Otsi kiiresti oma salvestatud DuckDuckGo paroole."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Otsi paroole"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Alusta hetkega DuckDuckGos privaatset otsingut."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Otsi kiiresti oma salvestatud DuckDuckGo paroole."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Otsi paroole"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Otsi või külasta oma lemmiksaite privaatselt vaid ühe puudutusega."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Külasta kiiresti oma lemmiksaite."; +/* Text in passwords widget */ +"widget.passwords" = "Otsi paroole"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Otsi DuckDuckGo'st"; diff --git a/Widgets/fi.lproj/Localizable.strings b/Widgets/fi.lproj/Localizable.strings index d3c3815e10..c3c5cc91e7 100644 --- a/Widgets/fi.lproj/Localizable.strings +++ b/Widgets/fi.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire-painike"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Hae nopeasti DuckDuckGohon tallennettuja salasanojasi."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Etsi salasanoja"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Aloita välittömästi yksityinen haku DuckDuckGossa."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Hae nopeasti DuckDuckGohon tallennettuja salasanojasi."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Etsi salasanoja"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Tee hakuja ja käy lempisivustoillasi yhdellä napautuksella."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Siirry nopeasti suosikkisivustoillesi."; +/* Text in passwords widget */ +"widget.passwords" = "Etsi salasanoja"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Hae DuckDuckGo:sta"; diff --git a/Widgets/fr.lproj/Localizable.strings b/Widgets/fr.lproj/Localizable.strings index 534645bedf..4be99b26a5 100644 --- a/Widgets/fr.lproj/Localizable.strings +++ b/Widgets/fr.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Recherchez rapidement vos mots de passe DuckDuckGo enregistrés."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Rechercher un mot de passe"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Lancez instantanément une recherche privée dans DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Recherchez rapidement vos mots de passe DuckDuckGo enregistrés."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Rechercher un mot de passe"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Recherchez ou visitez vos sites favoris en privé et d'un seul mouvement."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Consultez rapidement vos sites préférés."; +/* Text in passwords widget */ +"widget.passwords" = "Rechercher un mot de passe"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Rechercher avec DuckDuckGo"; diff --git a/Widgets/hr.lproj/Localizable.strings b/Widgets/hr.lproj/Localizable.strings index 6f30e34502..57c9e6786e 100644 --- a/Widgets/hr.lproj/Localizable.strings +++ b/Widgets/hr.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Brzo pretraži svoje spremljene DuckDuckGo lozinke."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Pretraživanje lozinki"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "U trenu pokreni privatno pretraživanje u DuckDuckGou."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Brzo pretraži svoje spremljene DuckDuckGo lozinke."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Pretraživanje lozinki"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Pretraži ili posjeti svoja omiljena web-mjesta privatno jednim dodirom."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Brzo posjeti svoja omiljena web-mjesta."; +/* Text in passwords widget */ +"widget.passwords" = "Pretraživanje lozinki"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Pretraži DuckDuckGo"; diff --git a/Widgets/hu.lproj/Localizable.strings b/Widgets/hu.lproj/Localizable.strings index 020c39e048..d3eaf530bb 100644 --- a/Widgets/hu.lproj/Localizable.strings +++ b/Widgets/hu.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Tűz gomb"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Gyorsan kereshetsz a DuckDuckGo mentett jelszavai között."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Jelszavak keresése"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Privát DuckDuckGo-keresés azonnali indítása."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Gyorsan kereshetsz a DuckDuckGo mentett jelszavai között."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Jelszavak keresése"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Látogasd meg a kedvenc weboldalaidat és keress rajtuk privát módon, mindössze egyetlen koppintással."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Gyorsan látogass el a kedvenc webhelyeidre."; +/* Text in passwords widget */ +"widget.passwords" = "Jelszavak keresése"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "DuckDuckGo keresés"; diff --git a/Widgets/it.lproj/Localizable.strings b/Widgets/it.lproj/Localizable.strings index 054ce6c494..df33b790b6 100644 --- a/Widgets/it.lproj/Localizable.strings +++ b/Widgets/it.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Cerca rapidamente le password DuckDuckGo salvate."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Cerca password"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Inizia subito una ricerca privata in DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Cerca rapidamente le password DuckDuckGo salvate."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Cerca password"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Effettua ricerche o visita i tuoi siti preferiti in incognito con un solo tocco."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Visita velocemente i tuoi siti preferiti."; +/* Text in passwords widget */ +"widget.passwords" = "Cerca password"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Cerca DuckDuckGo"; diff --git a/Widgets/lt.lproj/Localizable.strings b/Widgets/lt.lproj/Localizable.strings index 093743ccf9..7a5f84cb4d 100644 --- a/Widgets/lt.lproj/Localizable.strings +++ b/Widgets/lt.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Mygtukas „Fire“"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Greitai ieškokite išsaugotų „DuckDuckGo“ slaptažodžių."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Ieškoti slaptažodžių"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Akimirksniu pradėkite privačią paiešką „DuckDuckGo“."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Greitai ieškokite išsaugotų „DuckDuckGo“ slaptažodžių."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Ieškoti slaptažodžių"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Ieškokite arba lankykitės savo mėgstamose svetainėse privačiai vienu bakstelėjimu."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Greitai apsilankykite savo mėgstamose svetainėse."; +/* Text in passwords widget */ +"widget.passwords" = "Ieškoti slaptažodžių"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Ieškoti DuckDuckGo"; diff --git a/Widgets/lv.lproj/Localizable.strings b/Widgets/lv.lproj/Localizable.strings index 9a4f7e7289..e8e53097d3 100644 --- a/Widgets/lv.lproj/Localizable.strings +++ b/Widgets/lv.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Ātri meklē savas saglabātās DuckDuckGo paroles."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Meklēt paroles"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Nekavējoties sāc privātu meklēšanu pakalpojumā DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Ātri meklē savas saglabātās DuckDuckGo paroles."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Meklēt paroles"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Meklē vai atver savas iecienītākās vietnes konfidenciāli, veicot tikai vienu pieskārienu."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Ātri atver savas mīļākās vietnes."; +/* Text in passwords widget */ +"widget.passwords" = "Meklēt paroles"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Meklēt DuckDuckGo"; diff --git a/Widgets/nb.lproj/Localizable.strings b/Widgets/nb.lproj/Localizable.strings index f4be22a9a9..5315fab5f8 100644 --- a/Widgets/nb.lproj/Localizable.strings +++ b/Widgets/nb.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Søk raskt i de lagrede DuckDuckGo-passordene dine."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Søk i passord"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Start et privat søk i DuckDuckGo på et øyeblikk."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Søk raskt i de lagrede DuckDuckGo-passordene dine."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Søk i passord"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Søk eller besøk favorittsider privat med ett trykk."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Besøk favorittsidene dine raskt."; +/* Text in passwords widget */ +"widget.passwords" = "Søk i passord"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Søk i DuckDuckGo"; diff --git a/Widgets/nl.lproj/Localizable.strings b/Widgets/nl.lproj/Localizable.strings index 18e0f7e4f1..75679e3121 100644 --- a/Widgets/nl.lproj/Localizable.strings +++ b/Widgets/nl.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Zoek snel je opgeslagen DuckDuckGo-wachtwoorden."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Wachtwoorden zoeken"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Start direct een private zoekopdracht in DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Zoek snel je opgeslagen DuckDuckGo-wachtwoorden."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Wachtwoorden zoeken"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Zoek of bezoek je favoriete sites in de privémodus met één tik."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Snel toegang tot je favoriete sites."; +/* Text in passwords widget */ +"widget.passwords" = "Wachtwoorden zoeken"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Zoek in DuckDuckGo"; diff --git a/Widgets/pl.lproj/Localizable.strings b/Widgets/pl.lproj/Localizable.strings index a188c79e20..cf7b783a92 100644 --- a/Widgets/pl.lproj/Localizable.strings +++ b/Widgets/pl.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Szybko wyszukuj zapisane hasła DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Wyszukaj hasła"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Błyskawicznie rozpocznij prywatne wyszukiwanie w DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Szybko wyszukuj zapisane hasła DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Wyszukaj hasła"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Prywatnie przeszukuj lub odwiedzaj swoje ulubione witryny za pomocą jednego dotknięcia."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Szybko odwiedzaj swoje ulubione witryny."; +/* Text in passwords widget */ +"widget.passwords" = "Wyszukaj hasła"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Przeszukaj DuckDuckGo"; diff --git a/Widgets/pt.lproj/Localizable.strings b/Widgets/pt.lproj/Localizable.strings index 5727c566a8..b4c756609f 100644 --- a/Widgets/pt.lproj/Localizable.strings +++ b/Widgets/pt.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Pesquisa rapidamente as tuas palavras-passe guardadas do DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Pesquisar palavras-passe"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Inicia instantaneamente uma pesquisa privada no DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Pesquisa rapidamente as tuas palavras-passe guardadas do DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Pesquisar palavras-passe"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Pesquise ou visite os seus sites favoritos de forma privada com apenas um toque."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Visite rapidamente os seus sites favoritos."; +/* Text in passwords widget */ +"widget.passwords" = "Pesquisar palavras-passe"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Pesquisar no DuckDuckGo"; diff --git a/Widgets/ro.lproj/Localizable.strings b/Widgets/ro.lproj/Localizable.strings index 8a8bc2c5c3..02e3b66233 100644 --- a/Widgets/ro.lproj/Localizable.strings +++ b/Widgets/ro.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Caută rapid parolele tale DuckDuckGo salvate."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Caută parole"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Pornește instantaneu o căutare privată în DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Caută rapid parolele tale DuckDuckGo salvate."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Caută parole"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Caută sau vizitează site-urile preferate în mod privat cu o singură atingere."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Vizitează rapid site-urile preferate."; +/* Text in passwords widget */ +"widget.passwords" = "Caută parole"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Căutare DuckDuckGo"; diff --git a/Widgets/ru.lproj/Localizable.strings b/Widgets/ru.lproj/Localizable.strings index 8559bd3c26..16b1a1f59c 100644 --- a/Widgets/ru.lproj/Localizable.strings +++ b/Widgets/ru.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Быстрый поиск по паролям, сохраненным в DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Найти пароль"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Моментальный запуск приватного поиска в DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Быстрый поиск среди паролей, сохраненных в DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Найти пароль"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Поиск и посещение любимых сайтов без слежки всего в одно касание."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Переходите быстро на любимые сайты."; +/* Text in passwords widget */ +"widget.passwords" = "Найти пароль"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Поиск в DuckDuckGo"; diff --git a/Widgets/sk.lproj/Localizable.strings b/Widgets/sk.lproj/Localizable.strings index 817d0fd8b6..b1362fad22 100644 --- a/Widgets/sk.lproj/Localizable.strings +++ b/Widgets/sk.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Rýchlo vyhľadajte svoje uložené DuckDuckGo heslá."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Vyhľadávanie hesiel"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Okamžite spustíte súkromné vyhľadávanie v DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Rýchlo vyhľadajte svoje uložené DuckDuckGo heslá."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Vyhľadávanie hesiel"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Vyhľadávajte alebo navštevujte svoje obľúbené stránky súkromne iba pomocou jedného kliknutia."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Navštívte svoje obľúbené stránky rýchlo."; +/* Text in passwords widget */ +"widget.passwords" = "Vyhľadávanie hesiel"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Vyhľadávajte cez DuckDuckGo"; diff --git a/Widgets/sl.lproj/Localizable.strings b/Widgets/sl.lproj/Localizable.strings index 8f94f58b78..3307da1723 100644 --- a/Widgets/sl.lproj/Localizable.strings +++ b/Widgets/sl.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Hitro poiščite shranjena gesla DuckDuckGo."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Iskanje gesel"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Takoj začnite zasebno iskanje v DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Hitro poiščite shranjena gesla DuckDuckGo."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Iskanje gesel"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Samo z enim dotikom zasebno poiščite ali obiščite priljubljena spletna mesta."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Hitro obiščite svoja priljubljena spletna mesta."; +/* Text in passwords widget */ +"widget.passwords" = "Iskanje gesel"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Iščite po DuckDuckGo"; diff --git a/Widgets/sv.lproj/Localizable.strings b/Widgets/sv.lproj/Localizable.strings index 0d06e15192..ed6904ff0f 100644 --- a/Widgets/sv.lproj/Localizable.strings +++ b/Widgets/sv.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Sök snabbt bland dina sparade DuckDuckGo-lösenord."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Sök lösenord"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "Starta omedelbart en privat sökning i DuckDuckGo."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Sök snabbt bland dina sparade DuckDuckGo-lösenord."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Sök lösenord"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Gör sökningar eller besök dina favoritwebbplatser privat med ett enda tryck."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "Besök dina favoritwebbplatser snabbt."; +/* Text in passwords widget */ +"widget.passwords" = "Sök lösenord"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "Sök med DuckDuckGo"; diff --git a/Widgets/tr.lproj/Localizable.strings b/Widgets/tr.lproj/Localizable.strings index ac5a006384..5bca6f7fa7 100644 --- a/Widgets/tr.lproj/Localizable.strings +++ b/Widgets/tr.lproj/Localizable.strings @@ -31,6 +31,12 @@ /* Title shown to the user when adding the Fire Button lock screen widget */ "lock.screen.widget.fire.title" = "Fire Button"; +/* Description shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.description" = "Kayıtlı DuckDuckGo şifrelerinizi hızlıca arayın."; + +/* Title shown to the user when adding the Search Passwords lock screen widget */ +"lock.screen.widget.passwords.title" = "Şifreleri ara"; + /* Description shown to the user when adding the Search lock screen widget */ "lock.screen.widget.search.description" = "DuckDuckGo'da hemen özel bir arama yapın."; @@ -46,6 +52,12 @@ /* No comment provided by engineer. */ "VPN Not Configured" = "VPN Not Configured"; +/* Description of search passwords widget in widget gallery */ +"widget.gallery.passwords.description" = "Kayıtlı DuckDuckGo şifrelerinizi hızlıca arayın."; + +/* Display name for search passwords widget in widget gallery */ +"widget.gallery.passwords.display.name" = "Şifreleri ara"; + /* Description of search and favorites widget in widget gallery */ "widget.gallery.search.and.favorites.description" = "Tek bir dokunuşla favori sitelerinizi gizlice arayın veya ziyaret edin."; @@ -64,6 +76,9 @@ /* Message shown in the favorites widget empty state. */ "widget.no.favorites.message" = "En sevdiğiniz siteleri hızlıca ziyaret edin."; +/* Text in passwords widget */ +"widget.passwords" = "Şifreleri ara"; + /* Placeholder text in search field on the search and favorites widget */ "widget.search.duckduckgo" = "DuckDuckGo'da Ara"; From d8d72028b80821366b20a16012d69ebdbfae307a Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Wed, 10 Apr 2024 19:05:12 +0200 Subject: [PATCH 226/245] BSK release 133.1.0 (#2708) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index a95f00f316..5ce9c84e9f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10174,7 +10174,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 133.0.1; + version = 133.1.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2ca1858aed..35c1b9084d 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" : "39d74829150a9ecffea2f503c01851e54eda8ad1", - "version" : "133.0.1" + "revision" : "4699a5ff3d0669736e87f6da808884f245d80ede", + "version" : "133.1.0" } }, { From b8506b7b26eb544e6cbd59c8ab6c22d974c3fa7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Thu, 11 Apr 2024 13:32:18 +0200 Subject: [PATCH 227/245] Bring back accessibility identifiers for onboarding buttons (#2709) --- DuckDuckGo/OnboardingButtonsView.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/DuckDuckGo/OnboardingButtonsView.swift b/DuckDuckGo/OnboardingButtonsView.swift index 60c71cde0e..259e6c8dc8 100644 --- a/DuckDuckGo/OnboardingButtonsView.swift +++ b/DuckDuckGo/OnboardingButtonsView.swift @@ -36,6 +36,7 @@ struct OnboardingActions: View { }) .buttonStyle(PrimaryButtonStyle()) .disabled(!viewModel.isContinueEnabled) + .accessibilityIdentifier("Continue") Button(action: { self.secondaryAction?() @@ -43,6 +44,7 @@ struct OnboardingActions: View { Text(viewModel.secondaryButtonTitle) }) .buttonStyle(GhostButtonStyle()) + .accessibilityIdentifier("Skip") } } } From 2f1f4ea38d63fd45c8e30e9cc4d1673eee5f28a6 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Thu, 11 Apr 2024 13:10:10 +0100 Subject: [PATCH 228/245] soft revert history suggestions (#2711) --- Core/DefaultVariantManager.swift | 4 +-- DuckDuckGo/AutocompleteViewController.swift | 38 ++++++++++++++++++--- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Core/DefaultVariantManager.swift b/Core/DefaultVariantManager.swift index 98cbf59382..1b20f2829a 100644 --- a/Core/DefaultVariantManager.swift +++ b/Core/DefaultVariantManager.swift @@ -26,8 +26,8 @@ extension FeatureName { // Define your feature e.g.: // public static let experimentalFeature = FeatureName(rawValue: "experimentalFeature") - // Experiment coming soon public static let history = FeatureName(rawValue: "history") + public static let newSuggestionLogic = FeatureName(rawValue: "newSuggestionLogic") } public struct VariantIOS: Variant { @@ -63,7 +63,7 @@ public struct VariantIOS: Variant { VariantIOS(name: "sd", weight: doNotAllocate, isIncluded: When.always, features: []), VariantIOS(name: "se", weight: doNotAllocate, isIncluded: When.always, features: []), - VariantIOS(name: "mc", weight: 1, isIncluded: When.inEnglish, features: []), + VariantIOS(name: "mc", weight: 1, isIncluded: When.inEnglish, features: [.newSuggestionLogic]), VariantIOS(name: "md", weight: 1, isIncluded: When.inEnglish, features: [.history]), returningUser diff --git a/DuckDuckGo/AutocompleteViewController.swift b/DuckDuckGo/AutocompleteViewController.swift index a2fc59be5f..a94516a273 100644 --- a/DuckDuckGo/AutocompleteViewController.swift +++ b/DuckDuckGo/AutocompleteViewController.swift @@ -27,6 +27,7 @@ import CoreData import Persistence import History import Combine +import BrowserServicesKit class AutocompleteViewController: UIViewController { @@ -54,10 +55,16 @@ class AutocompleteViewController: UIViewController { private var historyCoordinator: HistoryCoordinating! private var bookmarksDatabase: CoreDataDatabase! private var appSettings: AppSettings! + private var variantManager: VariantManager! + private lazy var cachedBookmarks: CachedBookmarks = { CachedBookmarks(bookmarksDatabase) }() + private lazy var cachedBookmarksSearch: BookmarksStringSearch = { + BookmarksCachingSearch(bookmarksStore: CoreDataBookmarksSearchStore(bookmarksStore: bookmarksDatabase)) + }() + var backgroundColor: UIColor { appSettings.currentAddressBarPosition.isBottom ? UIColor(designSystemColor: .background) : @@ -82,7 +89,8 @@ class AutocompleteViewController: UIViewController { static func loadFromStoryboard(bookmarksDatabase: CoreDataDatabase, historyCoordinator: HistoryCoordinating, - appSettings: AppSettings = AppDependencyProvider.shared.appSettings) -> AutocompleteViewController { + appSettings: AppSettings = AppDependencyProvider.shared.appSettings, + variantManager: VariantManager = DefaultVariantManager()) -> AutocompleteViewController { let storyboard = UIStoryboard(name: "Autocomplete", bundle: nil) guard let controller = storyboard.instantiateInitialViewController() as? AutocompleteViewController else { fatalError("Failed to instatiate correct Autocomplete view controller") @@ -90,6 +98,7 @@ class AutocompleteViewController: UIViewController { controller.bookmarksDatabase = bookmarksDatabase controller.historyCoordinator = historyCoordinator controller.appSettings = appSettings + controller.variantManager = variantManager return controller } @@ -178,6 +187,16 @@ class AutocompleteViewController: UIViewController { selectedItem = -1 tableView.reloadData() + let bookmarks: [Suggestion] + + if variantManager.inSuggestionExperiment { + bookmarks = [] // We'll supply bookmarks elsewhere + } else { + bookmarks = cachedBookmarksSearch.search(query: query).prefix(2).map { + .bookmark(title: $0.title, url: $0.url, isFavorite: $0.isFavorite, allowedInTopHits: true) + } + } + loader = SuggestionLoader(dataSource: self, urlFactory: { phrase in guard let url = URL(trimmedAddressBarString: phrase), let scheme = url.scheme, @@ -195,7 +214,10 @@ class AutocompleteViewController: UIViewController { self?.pendingRequest = false } guard error == nil else { return } - self?.updateSuggestions(result?.all ?? []) + + let remoteResults = result?.all ?? [] + + self?.updateSuggestions(bookmarks + remoteResults) } } @@ -333,11 +355,11 @@ extension AutocompleteViewController { extension AutocompleteViewController: SuggestionLoadingDataSource { func history(for suggestionLoading: Suggestions.SuggestionLoading) -> [HistorySuggestion] { - return historyCoordinator.history ?? [] + return variantManager.inSuggestionExperiment ? (historyCoordinator.history ?? []) : [] } func bookmarks(for suggestionLoading: Suggestions.SuggestionLoading) -> [Suggestions.Bookmark] { - return cachedBookmarks.all + return variantManager.inSuggestionExperiment ? cachedBookmarks.all : [] } func suggestionLoading(_ suggestionLoading: Suggestions.SuggestionLoading, suggestionDataFromUrl url: URL, withParameters parameters: [String: String], completion: @escaping (Data?, Error?) -> Void) { @@ -363,3 +385,11 @@ extension HistoryEntry: HistorySuggestion { } } + +extension VariantManager { + + var inSuggestionExperiment: Bool { + isSupported(feature: .newSuggestionLogic) || isSupported(feature: .history) + } + +} From 9d43ddf36e365955ccdcb3036996b8ed0b14d285 Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Thu, 11 Apr 2024 13:31:08 +0100 Subject: [PATCH 229/245] Release 7.115.0-2 (#2712) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 8927490201..d16c6fe3a5 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8278,7 +8278,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8315,7 +8315,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8407,7 +8407,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8435,7 +8435,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8585,7 +8585,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8611,7 +8611,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8676,7 +8676,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8711,7 +8711,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8745,7 +8745,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8776,7 +8776,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9063,7 +9063,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9094,7 +9094,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9123,7 +9123,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9157,7 +9157,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9188,7 +9188,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9221,11 +9221,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9459,7 +9459,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9486,7 +9486,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9519,7 +9519,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9557,7 +9557,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9593,7 +9593,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9628,11 +9628,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9806,11 +9806,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9839,10 +9839,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = 2; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; + DYLIB_CURRENT_VERSION = 2; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From a19736c22c2353d1af7f41c9fd141ee4c5cfcfc2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 11 Apr 2024 20:00:49 +0200 Subject: [PATCH 230/245] Manually hide loader + Pixel (#2687) Description: During testing, a couple of users experienced the "Progress Indicator" not being dismissed after the subscription activated successfully. This fixes potential issues, plus adds a pixel to monitor and prevent widespread. --- Core/PixelEvent.swift | 2 + ...scriptionPagesUseSubscriptionFeature.swift | 156 ++++++++---------- .../ViewModel/SubscriptionFlowViewModel.swift | 32 ++++ 3 files changed, 107 insertions(+), 83 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 1e7119aa3c..4e3a0f78c3 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -593,6 +593,7 @@ extension Pixel { case privacyProPromotionDialogShownVPN case privacyProVPNAccessRevokedDialogShown case privacyProVPNBetaStoppedWhenPrivacyProEnabled + case privacyProTransactionProgressNotHiddenAfter60s // MARK: Pixel Experiment case pixelExperimentEnrollment @@ -1206,6 +1207,7 @@ extension Pixel.Event { case .privacyProSubscriptionManagementEmail: return "m_privacy-pro_manage-email_edit_click" case .privacyProSubscriptionManagementPlanBilling: return "m_privacy-pro_settings_change-plan-or-billing_click" case .privacyProSubscriptionManagementRemoval: return "m_privacy-pro_settings_remove-from-device_click" + case .privacyProTransactionProgressNotHiddenAfter60s: return "m_privacy-pro_progress_not_hidden_after_60s" // MARK: Pixel Experiment case .pixelExperimentEnrollment: return "pixel_experiment_enrollment" diff --git a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift index 7845e5ee46..e1bd6f27f6 100644 --- a/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift +++ b/DuckDuckGo/Subscription/UserScripts/SubscriptionPagesUseSubscriptionFeature.swift @@ -153,14 +153,6 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } // swiftlint:enable nesting - // Manage transaction in progress flag - private func withTransactionInProgress(_ work: () async -> T) async -> T { - setTransactionStatus(transactionStatus) - defer { - setTransactionStatus(.idle) - } - return await work() - } private func resetSubscriptionFlow() { setTransactionError(nil) @@ -182,94 +174,92 @@ final class SubscriptionPagesUseSubscriptionFeature: Subfeature, ObservableObjec } func getSubscriptionOptions(params: Any, original: WKScriptMessage) async -> Encodable? { - - await withTransactionInProgress { - - setTransactionStatus(.purchasing) - resetSubscriptionFlow() - - switch await AppStorePurchaseFlow.subscriptionOptions() { - case .success(let subscriptionOptions): - if AppDependencyProvider.shared.subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed { - return subscriptionOptions - } else { - return SubscriptionOptions.empty - } - case .failure: - os_log("Failed to obtain subscription options", log: .subscription, type: .error) - setTransactionError(.failedToGetSubscriptionOptions) - return nil + resetSubscriptionFlow() + + switch await AppStorePurchaseFlow.subscriptionOptions() { + case .success(let subscriptionOptions): + if AppDependencyProvider.shared.subscriptionFeatureAvailability.isSubscriptionPurchaseAllowed { + return subscriptionOptions + } else { + return SubscriptionOptions.empty } + case .failure: + os_log("Failed to obtain subscription options", log: .subscription, type: .error) + setTransactionError(.failedToGetSubscriptionOptions) + return nil } } // swiftlint:disable:next function_body_length func subscriptionSelected(params: Any, original: WKScriptMessage) async -> Encodable? { + + DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseAttempt) + setTransactionError(nil) + setTransactionStatus(.purchasing) + resetSubscriptionFlow() - await withTransactionInProgress { - DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseAttempt) - setTransactionError(nil) - setTransactionStatus(.purchasing) - resetSubscriptionFlow() - - struct SubscriptionSelection: Decodable { - let id: String - } + struct SubscriptionSelection: Decodable { + let id: String + } + + let message = original + guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { + assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + setTransactionStatus(.idle) + return nil + } + + // Check for active subscriptions + if await PurchaseManager.hasActiveSubscription() { + setTransactionError(.hasActiveSubscription) + Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) + setTransactionStatus(.idle) + return nil + } + + let emailAccessToken = try? EmailManager().getToken() + let purchaseTransactionJWS: String + + switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, + emailAccessToken: emailAccessToken, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { + case .success(let transactionJWS): + purchaseTransactionJWS = transactionJWS + + case .failure(let error): - let message = original - guard let subscriptionSelection: SubscriptionSelection = DecodableHelper.decode(from: params) else { - assertionFailure("SubscriptionPagesUserScript: expected JSON representation of SubscriptionSelection") + switch error { + case .cancelledByUser: + setTransactionError(.cancelledByUser) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) return nil - } - - // Check for active subscriptions - if await PurchaseManager.hasActiveSubscription() { + case .accountCreationFailed: + setTransactionError(.accountCreationFailed) + case .activeSubscriptionAlreadyPresent: setTransactionError(.hasActiveSubscription) - Pixel.fire(pixel: .privacyProRestoreAfterPurchaseAttempt) - return nil - } - - let emailAccessToken = try? EmailManager().getToken() - let purchaseTransactionJWS: String - - switch await AppStorePurchaseFlow.purchaseSubscription(with: subscriptionSelection.id, - emailAccessToken: emailAccessToken, - subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { - case .success(let transactionJWS): - purchaseTransactionJWS = transactionJWS - - case .failure(let error): - - switch error { - case .cancelledByUser: - setTransactionError(.cancelledByUser) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "canceled")) - return nil - case .accountCreationFailed: - setTransactionError(.accountCreationFailed) - case .activeSubscriptionAlreadyPresent: - setTransactionError(.hasActiveSubscription) - default: - setTransactionError(.purchaseFailed) - } - originalMessage = original - setTransactionStatus(.idle) - return nil - } - - setTransactionStatus(.polling) - switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, - subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { - case .success(let purchaseUpdate): - DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseSuccess) - UniquePixel.fire(pixel: .privacyProSubscriptionActivated) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) - case .failure: - setTransactionError(.missingEntitlements) - await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) + default: + setTransactionError(.purchaseFailed) } + originalMessage = original + setTransactionStatus(.idle) return nil } + + setTransactionStatus(.polling) + switch await AppStorePurchaseFlow.completeSubscriptionPurchase(with: purchaseTransactionJWS, + subscriptionAppGroup: Bundle.main.appGroup(bundle: .subs)) { + case .success(let purchaseUpdate): + DailyPixel.fireDailyAndCount(pixel: .privacyProPurchaseSuccess) + UniquePixel.fire(pixel: .privacyProSubscriptionActivated) + setTransactionStatus(.idle) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: purchaseUpdate) + case .failure: + setTransactionStatus(.idle) + setTransactionError(.missingEntitlements) + await pushPurchaseUpdate(originalMessage: message, purchaseUpdate: PurchaseUpdate(type: "completed")) + } + return nil + } func setSubscription(params: Any, original: WKScriptMessage) async -> Encodable? { diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index ec8e188e07..4c57a4db21 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -26,6 +26,7 @@ import Core #if SUBSCRIPTION import Subscription @available(iOS 15.0, *) +// swiftlint:disable type_body_length final class SubscriptionFlowViewModel: ObservableObject { let userScript: SubscriptionPagesUserScript @@ -38,6 +39,7 @@ final class SubscriptionFlowViewModel: ObservableObject { private var cancellables = Set() private var canGoBackCancellable: AnyCancellable? private var urlCancellable: AnyCancellable? + private var transactionStatusTimer: Timer? enum Constants { static let navigationBarHideThreshold = 80.0 @@ -101,6 +103,7 @@ final class SubscriptionFlowViewModel: ObservableObject { subFeature.onActivateSubscription = { DispatchQueue.main.async { self.state.shouldActivateSubscription = true + self.setTransactionStatus(.idle) } } @@ -125,6 +128,7 @@ final class SubscriptionFlowViewModel: ObservableObject { .removeDuplicates() .sink { [weak self] value in guard let strongSelf = self else { return } + Task { await strongSelf.setTransactionStatus(.idle) } if let value { Task { await strongSelf.handleTransactionError(error: value) } } @@ -140,6 +144,9 @@ final class SubscriptionFlowViewModel: ObservableObject { var isStoreError = false var isBackendError = false + // Reset the transaction Status + self.setTransactionStatus(.idle) + switch error { case .purchaseFailed: isStoreError = true @@ -201,6 +208,7 @@ final class SubscriptionFlowViewModel: ObservableObject { guard let strongSelf = self else { return } DispatchQueue.main.async { strongSelf.state.transactionError = error != nil ? .generalError : nil + strongSelf.setTransactionStatus(.idle) } } @@ -225,6 +233,7 @@ final class SubscriptionFlowViewModel: ObservableObject { guard let strongSelf = self else { return } strongSelf.state.canNavigateBack = false guard let currentURL = self?.webViewModel.url else { return } + Task { await strongSelf.setTransactionStatus(.idle) } if currentURL.forComparison() == URL.addEmailToSubscription.forComparison() || currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() || currentURL.forComparison() == URL.addEmailToSubscriptionSuccess.forComparison() { @@ -243,6 +252,7 @@ final class SubscriptionFlowViewModel: ObservableObject { } private func cleanUp() { + transactionStatusTimer?.invalidate() canGoBackCancellable?.cancel() urlCancellable?.cancel() subFeature.cleanup() @@ -251,11 +261,13 @@ final class SubscriptionFlowViewModel: ObservableObject { @MainActor func resetState() { + self.setTransactionStatus(.idle) self.state = State() } deinit { cleanUp() + transactionStatusTimer = nil canGoBackCancellable = nil urlCancellable = nil } @@ -263,6 +275,25 @@ final class SubscriptionFlowViewModel: ObservableObject { @MainActor private func setTransactionStatus(_ status: SubscriptionTransactionStatus) { self.state.transactionStatus = status + + // Fire a temp pixel if status is not back to idle in 60s + // Remove block when removing pixel + // https://app.asana.com/0/1204099484721401/1207003487111848/f + + // Invalidate existing timer if any + transactionStatusTimer?.invalidate() + + if status != .idle { + // Schedule a new timer + transactionStatusTimer = Timer.scheduledTimer(withTimeInterval: 60, repeats: false) { [weak self] _ in + guard let strongSelf = self else { return } + if strongSelf.state.transactionStatus != .idle { + Pixel.fire(pixel: .privacyProTransactionProgressNotHiddenAfter60s, error: nil) + } + strongSelf.transactionStatusTimer?.invalidate() + strongSelf.transactionStatusTimer = nil + } + } } @MainActor @@ -330,3 +361,4 @@ private extension URL { } } +// swiftlint:enable type_body_length From 174f46f4826a230179954772c2b786ac01baa6d8 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 11 Apr 2024 19:08:53 +0200 Subject: [PATCH 231/245] Add SubscriptionContainerViewModel and --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++- .../SubscriptionContainerViewModel.swift | 48 +++++++++++++++++++ .../SubscriptionEmailViewModel.swift | 1 - .../ViewModel/SubscriptionFlowViewModel.swift | 1 - .../SubscriptionRestoreViewModel.swift | 1 - .../Views/SubscriptionContainerView.swift | 18 +++---- 6 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fe965d8ce1..c395cc65c8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -845,6 +845,7 @@ D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */; }; D670E5BB2BB6A75300941A42 /* SubscriptionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */; }; D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */; }; + D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; @@ -2543,6 +2544,7 @@ D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerView.swift; sourceTree = ""; }; D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionNavigationCoordinator.swift; sourceTree = ""; }; D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AppearModifiers.swift"; sourceTree = ""; }; + D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewModel.swift; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; @@ -4769,13 +4771,14 @@ D664C7932B289AA000CBFA76 /* ViewModel */ = { isa = PBXGroup; children = ( + D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, + D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */, D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */, D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */, D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */, D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */, - D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -6947,6 +6950,7 @@ F194FAED1F14E2B3009B4DF8 /* UIFontExtension.swift in Sources */, F1CDD3F21F16911700BE0581 /* AboutViewControllerOld.swift in Sources */, 98F0FC2021FF18E700CE77AB /* AutoClearSettingsViewController.swift in Sources */, + D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */, 027F487A2A4B66CD001A1C6C /* AppTPFAQViewModel.swift in Sources */, F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */, diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift new file mode 100644 index 0000000000..38534d8805 --- /dev/null +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift @@ -0,0 +1,48 @@ +// +// SubscriptionContainerViewModel.swift +// DuckDuckGo +// +// 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 + +#if SUBSCRIPTION +@available(iOS 15.0, *) +final class SubscriptionContainerViewModel: ObservableObject { + + let userScript: SubscriptionPagesUserScript + let subFeature: SubscriptionPagesUseSubscriptionFeature + + let flow: SubscriptionFlowViewModel + let restore: SubscriptionRestoreViewModel + let email: SubscriptionEmailViewModel + + + init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), + subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature()) { + self.userScript = userScript + self.subFeature = subFeature + self.flow = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) + self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) + self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) + } + + deinit { + subFeature.cleanup() + } +} +#endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index bf101eed74..9ffba5e46c 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -116,7 +116,6 @@ final class SubscriptionEmailViewModel: ObservableObject { private func cleanUp() { canGoBackCancellable?.cancel() - subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 4c57a4db21..56177a19a5 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -255,7 +255,6 @@ final class SubscriptionFlowViewModel: ObservableObject { transactionStatusTimer?.invalidate() canGoBackCancellable?.cancel() urlCancellable?.cancel() - subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 015acfa306..8be54732f8 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -83,7 +83,6 @@ final class SubscriptionRestoreViewModel: ObservableObject { } private func cleanUp() { - subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift index 037e36740f..f006631188 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -31,18 +31,20 @@ struct SubscriptionContainerView: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator @State private var currentViewState: CurrentView + private let viewModel: SubscriptionContainerViewModel private let flowViewModel: SubscriptionFlowViewModel private let restoreViewModel: SubscriptionRestoreViewModel private let emailViewModel: SubscriptionEmailViewModel - - init(currentView: CurrentView) { - _currentViewState = State(initialValue: currentView) - let userScript = SubscriptionPagesUserScript() - let subFeature = SubscriptionPagesUseSubscriptionFeature() - flowViewModel = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) - restoreViewModel = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) - emailViewModel = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) + init(currentView: CurrentView, + viewModel: SubscriptionContainerViewModel = SubscriptionContainerViewModel()) { + _currentViewState = State(initialValue: currentView) + self.viewModel = viewModel + let userScript = viewModel.userScript + let subFeature = viewModel.subFeature + flowViewModel = viewModel.flow + restoreViewModel = viewModel.restore + emailViewModel = viewModel.email } var body: some View { From 68d6b0bdc20c894c45536dd323af5988bc207427 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 11 Apr 2024 20:34:29 +0200 Subject: [PATCH 232/245] Reverting accidental push to main (#2718) --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +-- .../SubscriptionContainerViewModel.swift | 48 ------------------- .../SubscriptionEmailViewModel.swift | 1 + .../ViewModel/SubscriptionFlowViewModel.swift | 1 + .../SubscriptionRestoreViewModel.swift | 1 + .../Views/SubscriptionContainerView.swift | 18 ++++--- 6 files changed, 12 insertions(+), 63 deletions(-) delete mode 100644 DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index c395cc65c8..fe965d8ce1 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -845,7 +845,6 @@ D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */; }; D670E5BB2BB6A75300941A42 /* SubscriptionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */; }; D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */; }; - D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; @@ -2544,7 +2543,6 @@ D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerView.swift; sourceTree = ""; }; D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionNavigationCoordinator.swift; sourceTree = ""; }; D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AppearModifiers.swift"; sourceTree = ""; }; - D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewModel.swift; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; @@ -4771,14 +4769,13 @@ D664C7932B289AA000CBFA76 /* ViewModel */ = { isa = PBXGroup; children = ( - D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, - D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */, D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */, D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */, D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */, D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */, + D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -6950,7 +6947,6 @@ F194FAED1F14E2B3009B4DF8 /* UIFontExtension.swift in Sources */, F1CDD3F21F16911700BE0581 /* AboutViewControllerOld.swift in Sources */, 98F0FC2021FF18E700CE77AB /* AutoClearSettingsViewController.swift in Sources */, - D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */, 027F487A2A4B66CD001A1C6C /* AppTPFAQViewModel.swift in Sources */, F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */, diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift deleted file mode 100644 index 38534d8805..0000000000 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// SubscriptionContainerViewModel.swift -// DuckDuckGo -// -// 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 - -#if SUBSCRIPTION -@available(iOS 15.0, *) -final class SubscriptionContainerViewModel: ObservableObject { - - let userScript: SubscriptionPagesUserScript - let subFeature: SubscriptionPagesUseSubscriptionFeature - - let flow: SubscriptionFlowViewModel - let restore: SubscriptionRestoreViewModel - let email: SubscriptionEmailViewModel - - - init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), - subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature()) { - self.userScript = userScript - self.subFeature = subFeature - self.flow = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) - self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) - self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) - } - - deinit { - subFeature.cleanup() - } -} -#endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index 9ffba5e46c..bf101eed74 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -116,6 +116,7 @@ final class SubscriptionEmailViewModel: ObservableObject { private func cleanUp() { canGoBackCancellable?.cancel() + subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 56177a19a5..4c57a4db21 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -255,6 +255,7 @@ final class SubscriptionFlowViewModel: ObservableObject { transactionStatusTimer?.invalidate() canGoBackCancellable?.cancel() urlCancellable?.cancel() + subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 8be54732f8..015acfa306 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -83,6 +83,7 @@ final class SubscriptionRestoreViewModel: ObservableObject { } private func cleanUp() { + subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift index f006631188..037e36740f 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -31,20 +31,18 @@ struct SubscriptionContainerView: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator @State private var currentViewState: CurrentView - private let viewModel: SubscriptionContainerViewModel private let flowViewModel: SubscriptionFlowViewModel private let restoreViewModel: SubscriptionRestoreViewModel private let emailViewModel: SubscriptionEmailViewModel - - init(currentView: CurrentView, - viewModel: SubscriptionContainerViewModel = SubscriptionContainerViewModel()) { + + init(currentView: CurrentView) { _currentViewState = State(initialValue: currentView) - self.viewModel = viewModel - let userScript = viewModel.userScript - let subFeature = viewModel.subFeature - flowViewModel = viewModel.flow - restoreViewModel = viewModel.restore - emailViewModel = viewModel.email + + let userScript = SubscriptionPagesUserScript() + let subFeature = SubscriptionPagesUseSubscriptionFeature() + flowViewModel = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) + restoreViewModel = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) + emailViewModel = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) } var body: some View { From 5fc55ed154972491f951b8727bab4df8f7e7060e Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Thu, 11 Apr 2024 21:08:34 -0700 Subject: [PATCH 233/245] Remove timezone offset from the VPN server object (#2701) Task/Issue URL: https://app.asana.com/0/414235014887631/1207032029127388/f Tech Design URL: CC: Description: This PR removes support for tzOffset, which is no longer used. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fe965d8ce1..42df8a3aef 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10174,7 +10174,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 133.1.0; + version = 134.0.0; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 35c1b9084d..32226596b2 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" : "4699a5ff3d0669736e87f6da808884f245d80ede", - "version" : "133.1.0" + "revision" : "bc70d1a27263cc97a4060ac9e73ec10929c28a29", + "version" : "134.0.0" } }, { From d8673d0dd7d93cbb2726e09e01908849dcaf5f6a Mon Sep 17 00:00:00 2001 From: Christopher Brind Date: Fri, 12 Apr 2024 09:37:06 +0100 Subject: [PATCH 234/245] maestro: hide dax dialogs if visible and cancel keyboard after fireproof (#2695) Task/Issue URL: https://app.asana.com/0/414235014887631/1207014419221336/f Tech Design URL: CC: Description: Improves the stability of the fire proofing end to end test. Adds short cut to hide dax dialogs. Steps to test this PR: Run the test locally Admire the passing state of the tests here https://github.com/duckduckgo/iOS/actions/runs/8599167350/job/23561488619 --- .maestro/data_clearing_tests/01_fire_proofing.yml | 4 ++++ .maestro/shared/hide_daxdialogs.yaml | 11 +++++++++++ 2 files changed, 15 insertions(+) create mode 100644 .maestro/shared/hide_daxdialogs.yaml diff --git a/.maestro/data_clearing_tests/01_fire_proofing.yml b/.maestro/data_clearing_tests/01_fire_proofing.yml index 9dd1d4b5a3..26b0180ecd 100644 --- a/.maestro/data_clearing_tests/01_fire_proofing.yml +++ b/.maestro/data_clearing_tests/01_fire_proofing.yml @@ -21,6 +21,8 @@ tags: id: "searchEntry" - inputText: "https://setcookie.net" - pressKey: Enter +- runFlow: + file: ../shared/hide_daxdialogs.yaml # Set a cookie - assertVisible: "Cookie Test" @@ -44,6 +46,8 @@ tags: - tapOn: "Close Tabs and Clear Data" - tapOn: id: "alert.forget-data.confirm" +- assertVisible: "Cancel" +- tapOn: "Cancel" - assertVisible: id: "searchEntry" - tapOn: "Close Tabs and Clear Data" diff --git a/.maestro/shared/hide_daxdialogs.yaml b/.maestro/shared/hide_daxdialogs.yaml new file mode 100644 index 0000000000..8d58dec231 --- /dev/null +++ b/.maestro/shared/hide_daxdialogs.yaml @@ -0,0 +1,11 @@ +appId: com.duckduckgo.mobile.ios +--- + +- runFlow: + when: + visible: + id: "DaxIcon" + commands: + - tapOn: "Hide" + - tapOn: "Hide Tips Forever" + From 7170656813e6d7cdb3a36b2039508436c5092c96 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 12 Apr 2024 10:44:38 +0200 Subject: [PATCH 235/245] =?UTF-8?q?Manage=20=E2=80=98Stale=E2=80=99=20PRs?= =?UTF-8?q?=20(#2723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/414709148257752/1207048535807038/f Description: Marks inactive PRs as 'stale' after 7 days Closes inactive PRs after 14 days --- .github/workflows/stale-pr.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/stale-pr.yml diff --git a/.github/workflows/stale-pr.yml b/.github/workflows/stale-pr.yml new file mode 100644 index 0000000000..a5590c9c63 --- /dev/null +++ b/.github/workflows/stale-pr.yml @@ -0,0 +1,19 @@ +name: Close Stale Pull Requests + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + close_stale_prs: + runs-on: ubuntu-latest + steps: + - name: Close stale pull requests + uses: actions/stale@v9 + with: + stale-pr-message: 'This PR has been inactive for more than 7 days and will be automatically closed 7 days from now.' + days-before-stale: 7 + close-pr-message: 'This PR has been closed after 14 days of inactivity. Feel free to reopen it if you plan to continue working on it or have further discussions.' + days-before-close: 7 + stale-pr-label: stale + exempt-draft-pr: true \ No newline at end of file From 44e61afe9fe8c05f6a4bc3797f2c2b68b486092d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 12 Apr 2024 12:34:29 +0200 Subject: [PATCH 236/245] Subscriptions: Fix thread issue on Subscription Restore (#2719) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ask/Issue URL: https://app.asana.com/0/1204099484721401/1207054737385929/f Description: There is a threading issue on deInit for the SubscriptionFlow and SubscriptionRestore Viewmodels causing simultaneous calls to cleanup() in the SubFeature. (While I was unable to reproduce the issue, it’s there.) Since subFeature is now a single instance everywhere, this adds a new ViewModel that holds and cleans the subFeature object instead of delegating the tasks to the internal viewModels. --- DuckDuckGo.xcodeproj/project.pbxproj | 6 ++- .../SubscriptionContainerViewModel.swift | 48 +++++++++++++++++++ .../SubscriptionEmailViewModel.swift | 1 - .../ViewModel/SubscriptionFlowViewModel.swift | 1 - .../SubscriptionRestoreViewModel.swift | 1 - .../Views/SubscriptionContainerView.swift | 18 +++---- 6 files changed, 63 insertions(+), 12 deletions(-) create mode 100644 DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 42df8a3aef..919c338a8d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -845,6 +845,7 @@ D66F683D2BB333C100AE93E2 /* SubscriptionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */; }; D670E5BB2BB6A75300941A42 /* SubscriptionNavigationCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */; }; D670E5BD2BB6AA0000941A42 /* View+AppearModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */; }; + D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */; }; D68A21442B7EC08500BB372E /* SubscriptionExternalLinkView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */; }; D68A21462B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */; }; D68DF81C2B58302E0023DBEA /* SubscriptionRestoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */; }; @@ -2543,6 +2544,7 @@ D66F683C2BB333C100AE93E2 /* SubscriptionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerView.swift; sourceTree = ""; }; D670E5BA2BB6A75200941A42 /* SubscriptionNavigationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionNavigationCoordinator.swift; sourceTree = ""; }; D670E5BC2BB6AA0000941A42 /* View+AppearModifiers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+AppearModifiers.swift"; sourceTree = ""; }; + D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionContainerViewModel.swift; sourceTree = ""; }; D68A21432B7EC08500BB372E /* SubscriptionExternalLinkView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkView.swift; sourceTree = ""; }; D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionExternalLinkViewModel.swift; sourceTree = ""; }; D68DF81B2B58302E0023DBEA /* SubscriptionRestoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionRestoreView.swift; sourceTree = ""; }; @@ -4769,13 +4771,14 @@ D664C7932B289AA000CBFA76 /* ViewModel */ = { isa = PBXGroup; children = ( + D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, + D67969102BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift */, D664C7942B289AA000CBFA76 /* SubscriptionFlowViewModel.swift */, D68DF81D2B5830380023DBEA /* SubscriptionRestoreViewModel.swift */, D668D9262B6937D2008E2FF2 /* SubscriptionITPViewModel.swift */, D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */, D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */, D6BFCB602B7525160051FF81 /* SubscriptionPIRViewModel.swift */, - D68A21452B7EC16200BB372E /* SubscriptionExternalLinkViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -6947,6 +6950,7 @@ F194FAED1F14E2B3009B4DF8 /* UIFontExtension.swift in Sources */, F1CDD3F21F16911700BE0581 /* AboutViewControllerOld.swift in Sources */, 98F0FC2021FF18E700CE77AB /* AutoClearSettingsViewController.swift in Sources */, + D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */, 027F487A2A4B66CD001A1C6C /* AppTPFAQViewModel.swift in Sources */, F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, F17922DE1E7192E6006E3D97 /* SuggestionTableViewCell.swift in Sources */, diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift new file mode 100644 index 0000000000..38534d8805 --- /dev/null +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionContainerViewModel.swift @@ -0,0 +1,48 @@ +// +// SubscriptionContainerViewModel.swift +// DuckDuckGo +// +// 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 + +#if SUBSCRIPTION +@available(iOS 15.0, *) +final class SubscriptionContainerViewModel: ObservableObject { + + let userScript: SubscriptionPagesUserScript + let subFeature: SubscriptionPagesUseSubscriptionFeature + + let flow: SubscriptionFlowViewModel + let restore: SubscriptionRestoreViewModel + let email: SubscriptionEmailViewModel + + + init(userScript: SubscriptionPagesUserScript = SubscriptionPagesUserScript(), + subFeature: SubscriptionPagesUseSubscriptionFeature = SubscriptionPagesUseSubscriptionFeature()) { + self.userScript = userScript + self.subFeature = subFeature + self.flow = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) + self.restore = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) + self.email = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) + } + + deinit { + subFeature.cleanup() + } +} +#endif diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift index bf101eed74..9ffba5e46c 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionEmailViewModel.swift @@ -116,7 +116,6 @@ final class SubscriptionEmailViewModel: ObservableObject { private func cleanUp() { canGoBackCancellable?.cancel() - subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift index 4c57a4db21..56177a19a5 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionFlowViewModel.swift @@ -255,7 +255,6 @@ final class SubscriptionFlowViewModel: ObservableObject { transactionStatusTimer?.invalidate() canGoBackCancellable?.cancel() urlCancellable?.cancel() - subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift index 015acfa306..8be54732f8 100644 --- a/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift +++ b/DuckDuckGo/Subscription/ViewModel/SubscriptionRestoreViewModel.swift @@ -83,7 +83,6 @@ final class SubscriptionRestoreViewModel: ObservableObject { } private func cleanUp() { - subFeature.cleanup() cancellables.removeAll() } diff --git a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift index 037e36740f..f006631188 100644 --- a/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift +++ b/DuckDuckGo/Subscription/Views/SubscriptionContainerView.swift @@ -31,18 +31,20 @@ struct SubscriptionContainerView: View { @Environment(\.dismiss) var dismiss @EnvironmentObject var subscriptionNavigationCoordinator: SubscriptionNavigationCoordinator @State private var currentViewState: CurrentView + private let viewModel: SubscriptionContainerViewModel private let flowViewModel: SubscriptionFlowViewModel private let restoreViewModel: SubscriptionRestoreViewModel private let emailViewModel: SubscriptionEmailViewModel - - init(currentView: CurrentView) { - _currentViewState = State(initialValue: currentView) - let userScript = SubscriptionPagesUserScript() - let subFeature = SubscriptionPagesUseSubscriptionFeature() - flowViewModel = SubscriptionFlowViewModel(userScript: userScript, subFeature: subFeature) - restoreViewModel = SubscriptionRestoreViewModel(userScript: userScript, subFeature: subFeature) - emailViewModel = SubscriptionEmailViewModel(userScript: userScript, subFeature: subFeature) + init(currentView: CurrentView, + viewModel: SubscriptionContainerViewModel = SubscriptionContainerViewModel()) { + _currentViewState = State(initialValue: currentView) + self.viewModel = viewModel + let userScript = viewModel.userScript + let subFeature = viewModel.subFeature + flowViewModel = viewModel.flow + restoreViewModel = viewModel.restore + emailViewModel = viewModel.email } var body: some View { From 33d7208692c73c9a0bc4ae733b7b0b1b939f6334 Mon Sep 17 00:00:00 2001 From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Date: Fri, 12 Apr 2024 12:46:53 +0200 Subject: [PATCH 237/245] update bsk dependency (#2725) Task/Issue URL: https://app.asana.com/0/414235014887631/1206880509171835/f Description: Update BSK dependency --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 919c338a8d..0459942261 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10178,7 +10178,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 134.0.0; + version = 134.0.1; }; }; 9F8FE9472BAE50E50071E372 /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 32226596b2..116351bdf6 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" : "bc70d1a27263cc97a4060ac9e73ec10929c28a29", - "version" : "134.0.0" + "revision" : "b0749d25996c0fa18be07b7851f02ebb3b9fab50", + "version" : "134.0.1" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "62d5dc3d02f6a8347dc5f0b52162a0107d38b74c", - "version" : "5.8.0" + "revision" : "1bb3bc5eb565735051f342a87b5405d4374876c7", + "version" : "5.12.0" } }, { @@ -122,8 +122,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/privacy-dashboard", "state" : { - "revision" : "620921fea14569eb00745cb5a44890d5890d99ec", - "version" : "3.4.0" + "revision" : "14b13d0c3db38f471ce4ba1ecb502ee1986c84d7", + "version" : "3.5.0" } }, { From 7e9d9bb1fb8d4511b571ab4221cc1f8a3a1dd877 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 12 Apr 2024 12:54:25 +0200 Subject: [PATCH 238/245] VPN: Specific TunnelController start failure reporting (#2714) --- .../NetworkProtectionTunnelController.swift | 59 +++++++++++++++---- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index aaba16d63b..ea50513c9a 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -37,9 +37,36 @@ final class NetworkProtectionTunnelController: TunnelController { // MARK: - Starting & Stopping the VPN - enum StartError: LocalizedError { - case connectionStatusInvalid + enum StartError: LocalizedError, CustomNSError { case simulateControllerFailureError + case loadFromPreferencesFailed(Error) + case saveToPreferencesFailed(Error) + case startVPNFailed(Error) + case fetchAuthTokenFailed(Error) + + public var errorCode: Int { + switch self { + case .simulateControllerFailureError: 0 + case .loadFromPreferencesFailed: 1 + case .saveToPreferencesFailed: 2 + case .startVPNFailed: 3 + case .fetchAuthTokenFailed: 4 + } + } + + public var errorUserInfo: [String: Any] { + switch self { + case + .simulateControllerFailureError: + return [:] + case + .loadFromPreferencesFailed(let error), + .saveToPreferencesFailed(let error), + .startVPNFailed(let error), + .fetchAuthTokenFailed(let error): + return [NSUnderlyingErrorKey: error] + } + } } init() { @@ -147,7 +174,11 @@ final class NetworkProtectionTunnelController: TunnelController { } options["activationAttemptId"] = UUID().uuidString as NSString - options["authToken"] = try tokenStore.fetchToken() as NSString? + do { + options["authToken"] = try tokenStore.fetchToken() as NSString? + } catch { + throw StartError.fetchAuthTokenFailed(error) + } options[NetworkProtectionOptionKey.selectedEnvironment] = VPNSettings(defaults: .networkProtectionGroupDefaults) .selectedEnvironment.rawValue as NSString @@ -161,7 +192,7 @@ final class NetworkProtectionTunnelController: TunnelController { } } catch { Pixel.fire(pixel: .networkProtectionActivationRequestFailed, error: error) - throw error + throw StartError.startVPNFailed(error) } } @@ -203,17 +234,24 @@ final class NetworkProtectionTunnelController: TunnelController { private func setupAndSave(_ tunnelManager: NETunnelProviderManager) async throws { setup(tunnelManager) - try await tunnelManager.saveToPreferences() - try await tunnelManager.loadFromPreferences() - try await tunnelManager.saveToPreferences() + try await saveToPreferences(tunnelManager) + try await loadFromPreferences(tunnelManager) + try await saveToPreferences(tunnelManager) } private func saveToPreferences(_ tunnelManager: NETunnelProviderManager) async throws { do { try await tunnelManager.saveToPreferences() } catch { - Pixel.fire(pixel: .networkProtectionFailedToSaveToPreferences, error: error) - throw error + let nsError = error as NSError + if nsError.code == NEVPNError.Code.configurationReadWriteFailed.rawValue, + nsError.localizedDescription == "permission denied" { + // This is a user denying the system permissions prompt to add the config + // Maybe we should fire another pixel here, but not a start failure as this is an imaginable scenario + // The code could be caused by a number of problems so I'm using the localizedDescription to catch that case + return + } + throw StartError.saveToPreferencesFailed(error) } } @@ -221,8 +259,7 @@ final class NetworkProtectionTunnelController: TunnelController { do { try await tunnelManager.loadFromPreferences() } catch { - Pixel.fire(pixel: .networkProtectionFailedToLoadFromPreferences, error: error) - throw error + throw StartError.loadFromPreferencesFailed(error) } } From a12eca95d82ad7bfd12a7dde7b189e817b91bf77 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 12 Apr 2024 13:39:25 +0200 Subject: [PATCH 239/245] Release 7.115.0-3 (#2727) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d16c6fe3a5..662ae3da31 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8278,7 +8278,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8315,7 +8315,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8407,7 +8407,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8435,7 +8435,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8585,7 +8585,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8611,7 +8611,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8676,7 +8676,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8711,7 +8711,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8745,7 +8745,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8776,7 +8776,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9063,7 +9063,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9094,7 +9094,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9123,7 +9123,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9157,7 +9157,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9188,7 +9188,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9221,11 +9221,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9459,7 +9459,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9486,7 +9486,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9519,7 +9519,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9557,7 +9557,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9593,7 +9593,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9628,11 +9628,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9806,11 +9806,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9839,10 +9839,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 2; + CURRENT_PROJECT_VERSION = 3; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 2; + DYLIB_CURRENT_VERSION = 3; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From e9241917e375363e2877938aa623dcc130015219 Mon Sep 17 00:00:00 2001 From: Federico Cappelli Date: Fri, 12 Apr 2024 12:50:11 +0100 Subject: [PATCH 240/245] SPM updated: SwiftSoup, Lottie, ZIPFoundation (#2724) --- DuckDuckGo.xcodeproj/project.pbxproj | 6 +++--- .../xcshareddata/swiftpm/Package.resolved | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0459942261..2e2e4a777e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -10170,7 +10170,7 @@ repositoryURL = "https://github.com/weichsel/ZIPFoundation.git"; requirement = { kind = exactVersion; - version = 0.9.18; + version = 0.9.19; }; }; 98A16C2928A11BDE00A6C003 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */ = { @@ -10186,7 +10186,7 @@ repositoryURL = "https://github.com/airbnb/lottie-spm.git"; requirement = { kind = exactVersion; - version = 4.4.1; + version = 4.4.2; }; }; B6F997C22B8F374300476735 /* XCRemoteSwiftPackageReference "apple-toolbox" */ = { @@ -10202,7 +10202,7 @@ repositoryURL = "https://github.com/scinfu/SwiftSoup"; requirement = { kind = exactVersion; - version = 2.7.1; + version = 2.7.2; }; }; F1D43AF82B99C1D300BAB743 /* XCRemoteSwiftPackageReference "BareBonesBrowser" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 116351bdf6..8523aeeb34 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -104,8 +104,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/airbnb/lottie-spm.git", "state" : { - "revision" : "3bd43e12d6fb54654366a61f7cfaca787318b8ce", - "version" : "4.4.1" + "revision" : "4d0c11c85f5a9ec3d1b0daf2dc6daebc0e2df897", + "version" : "4.4.2" } }, { @@ -167,8 +167,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/scinfu/SwiftSoup", "state" : { - "revision" : "1d39e56d364cba79ce43b341f9661b534cccb18d", - "version" : "2.7.1" + "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", + "version" : "2.7.2" } }, { @@ -203,8 +203,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/weichsel/ZIPFoundation.git", "state" : { - "revision" : "b979e8b52c7ae7f3f39fa0182e738e9e7257eb78", - "version" : "0.9.18" + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" } } ], From 9261f84faa0b1b37e9a762a3a3b369fc22e85299 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 12 Apr 2024 14:04:48 +0200 Subject: [PATCH 241/245] Small UI Fixes for subscriptions (#2690) Task/Issue URL: https://app.asana.com/0/1204099484721401/1207003662391345/f Description: A few minor UI changes. - Added a title to the VPN navigation bar - Added a loader to Restore View --- DuckDuckGo/NetworkProtectionRootViewController.swift | 1 + .../ViewModel/SubscriptionRestoreViewModel.swift | 5 +---- DuckDuckGo/Subscription/Views/SubscriptionRestoreView.swift | 2 ++ 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionRootViewController.swift b/DuckDuckGo/NetworkProtectionRootViewController.swift index c3b1a20478..010aa988f6 100644 --- a/DuckDuckGo/NetworkProtectionRootViewController.swift +++ b/DuckDuckGo/NetworkProtectionRootViewController.swift @@ -32,6 +32,7 @@ final class NetworkProtectionRootViewController: UIHostingController Date: Fri, 12 Apr 2024 15:03:24 +0200 Subject: [PATCH 242/245] Fix VPN denial prompt loop (#2728) --- DuckDuckGo/NetworkProtectionTunnelController.swift | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtectionTunnelController.swift index ea50513c9a..5e0d632537 100644 --- a/DuckDuckGo/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtectionTunnelController.swift @@ -43,6 +43,7 @@ final class NetworkProtectionTunnelController: TunnelController { case saveToPreferencesFailed(Error) case startVPNFailed(Error) case fetchAuthTokenFailed(Error) + case configSystemPermissionsDenied(Error) public var errorCode: Int { switch self { @@ -51,6 +52,7 @@ final class NetworkProtectionTunnelController: TunnelController { case .saveToPreferencesFailed: 2 case .startVPNFailed: 3 case .fetchAuthTokenFailed: 4 + case .configSystemPermissionsDenied: 5 } } @@ -63,7 +65,8 @@ final class NetworkProtectionTunnelController: TunnelController { .loadFromPreferencesFailed(let error), .saveToPreferencesFailed(let error), .startVPNFailed(let error), - .fetchAuthTokenFailed(let error): + .fetchAuthTokenFailed(let error), + .configSystemPermissionsDenied(let error): return [NSUnderlyingErrorKey: error] } } @@ -82,6 +85,9 @@ final class NetworkProtectionTunnelController: TunnelController { try await startWithError() Pixel.fire(pixel: .networkProtectionControllerStartSuccess) } catch { + if case StartError.configSystemPermissionsDenied = error { + return + } Pixel.fire(pixel: .networkProtectionControllerStartFailure, error: error) #if DEBUG @@ -249,7 +255,7 @@ final class NetworkProtectionTunnelController: TunnelController { // This is a user denying the system permissions prompt to add the config // Maybe we should fire another pixel here, but not a start failure as this is an imaginable scenario // The code could be caused by a number of problems so I'm using the localizedDescription to catch that case - return + throw StartError.configSystemPermissionsDenied(error) } throw StartError.saveToPreferencesFailed(error) } From 43654620e037902808b818be716a0a68767c2838 Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Fri, 12 Apr 2024 15:37:49 +0200 Subject: [PATCH 243/245] Release 7.115.0-4 (#2729) --- DuckDuckGo.xcodeproj/project.pbxproj | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 662ae3da31..ca2b95d6e9 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8278,7 +8278,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProvider.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -8315,7 +8315,7 @@ CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8407,7 +8407,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8435,7 +8435,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -8585,7 +8585,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8611,7 +8611,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGo.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; INFOPLIST_FILE = DuckDuckGo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8676,7 +8676,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -8711,7 +8711,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -8745,7 +8745,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -8776,7 +8776,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9063,7 +9063,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9094,7 +9094,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ShareExtension/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9123,7 +9123,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = OpenAction/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; @@ -9157,7 +9157,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEAD_CODE_STRIPPING = NO; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = Widgets/Info.plist; @@ -9188,7 +9188,7 @@ CODE_SIGN_ENTITLEMENTS = PacketTunnelProvider/PacketTunnelProviderAlpha.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -9221,11 +9221,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 4; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9459,7 +9459,7 @@ CODE_SIGN_ENTITLEMENTS = DuckDuckGo/DuckDuckGoAlpha.entitlements; CODE_SIGN_IDENTITY = "iPhone Distribution"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9486,7 +9486,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9519,7 +9519,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9557,7 +9557,7 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEAD_CODE_STRIPPING = NO; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; @@ -9593,7 +9593,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; CODE_SIGN_STYLE = Manual; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_TEAM = ""; "DEVELOPMENT_TEAM[sdk=iphoneos*]" = HKE973VLUW; GCC_C_LANGUAGE_STANDARD = gnu11; @@ -9628,11 +9628,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 4; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9806,11 +9806,11 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 4; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -9839,10 +9839,10 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 3; + DYLIB_CURRENT_VERSION = 4; DYLIB_INSTALL_NAME_BASE = "@rpath"; INFOPLIST_FILE = Core/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; From 12284e3bf61d5115fbb124d226c9096dff9f7343 Mon Sep 17 00:00:00 2001 From: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:04:08 +0200 Subject: [PATCH 244/245] fix tests (#2732) --- .maestro/shared/onboarding.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.maestro/shared/onboarding.yaml b/.maestro/shared/onboarding.yaml index 4346f64b33..d624d49521 100644 --- a/.maestro/shared/onboarding.yaml +++ b/.maestro/shared/onboarding.yaml @@ -11,4 +11,4 @@ appId: com.duckduckgo.mobile.ios index: 0 - assertVisible: "Make DuckDuckGo your default browser." - tapOn: - id: "Skip" + text: "Skip" From 398bed389d8fa5c9f9408aecc4950b3dc3190649 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Fri, 12 Apr 2024 17:12:29 +0200 Subject: [PATCH 245/245] Fix inconsistent bars state when scrolling (#2733) --- DuckDuckGo/MainViewController.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 49b978243b..ad7c7a025f 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -1562,12 +1562,12 @@ extension MainViewController: BrowserChromeDelegate { let updateBlock = { self.updateToolbarConstant(percent) self.updateNavBarConstant(percent) - - self.view.layoutIfNeeded() - + self.viewCoordinator.navigationBarContainer.alpha = percent self.viewCoordinator.tabBarContainer.alpha = percent self.viewCoordinator.toolbar.alpha = percent + + self.view.layoutIfNeeded() } if animated {