diff --git a/.github/workflows/promote_testflight.yml b/.github/workflows/promote_testflight.yml new file mode 100644 index 0000000000..641c945ad7 --- /dev/null +++ b/.github/workflows/promote_testflight.yml @@ -0,0 +1,33 @@ +name: Promote TestFlight to App Store + +on: + workflow_dispatch: {} + +jobs: + promote-testflight-to-appstore: + runs-on: macos-14 + + steps: + - name: Check out the code + uses: actions/checkout@v4 + with: + ref: ${{ github.ref_name }} + sparse-checkout: | + .github + Gemfile + Gemfile.lock + fastlane + scripts + + - name: Set up fastlane + run: bundle install + + - name: Promote TestFlight to App Store + env: + APPLE_API_KEY_BASE64: ${{ secrets.APPLE_API_KEY_BASE64 }} + APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }} + APPLE_API_KEY_ISSUER: ${{ secrets.APPLE_API_KEY_ISSUER }} + run: | + git config --global user.name "Dax the Duck" + git config --global user.email "dax@duckduckgo.com" + bundle exec fastlane promote_latest_testflight_to_appstore diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 525a57ddcf..50a4fefb6f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: destination: description: "Upload destination (App Store or TestFlight)" required: true - default: appstore + default: testflight type: choice options: - appstore @@ -36,10 +36,9 @@ jobs: id: destination run: | INPUT_DESTINATION=${{ github.event.inputs.destination }} - echo "destination=${INPUT_DESTINATION:-"appstore"}" >> $GITHUB_OUTPUT + echo "destination=${INPUT_DESTINATION:-"testflight"}" >> $GITHUB_OUTPUT - name: Assert release branch - if: steps.destination.outputs.destination == 'appstore' run: | case "${{ github.ref }}" in *release/*) ;; diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 5a76c2767c..e99711a2c3 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 7.142.0 +MARKETING_VERSION = 7.143.0 diff --git a/Core/AppPrivacyConfigurationDataProvider.swift b/Core/AppPrivacyConfigurationDataProvider.swift index c044c7c70a..15f2c36375 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 = "\"6098a36b63dcb92404871b8b6c92a46e\"" - public static let embeddedDataSHA = "a72538e90ef1aba77d30dde1379e7344fe9f3ca1655796c9b555bed412daa205" + public static let embeddedDataETag = "\"f8b9cfd5f1eb7b77c21d4476f85bd177\"" + public static let embeddedDataSHA = "c26c97714d73a9e1e99dbd341d5890da42b49d34a296672be3d3cea00bdd37a0" } public var embeddedDataEtag: String { diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index b1d979190e..3a63769bb9 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -42,6 +42,7 @@ public enum FeatureFlag: String { case syncPromotionBookmarks case syncPromotionPasswords case onboardingHighlights + case onboardingAddToDock case autofillSurveys case autcompleteTabs @@ -94,6 +95,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(SyncPromotionSubfeature.passwords)) case .onboardingHighlights: return .internalOnly + case .onboardingAddToDock: + return .internalOnly case .autofillSurveys: return .remoteReleasable(.feature(.autofillSurveys)) case .autcompleteTabs: diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 4ba9c88b32..47b48f1333 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -708,6 +708,11 @@ extension Pixel { case privacyProTransactionProgressNotHiddenAfter60s case privacyProSuccessfulSubscriptionAttribution case privacyProKeychainAccessError + case privacyProSubscriptionCookieMissingTokenOnSignIn + case privacyProSubscriptionCookieMissingCookieOnSignOut + case privacyProSubscriptionCookieRefreshedWithAccessToken + case privacyProSubscriptionCookieRefreshedWithEmptyValue + case privacyProSubscriptionCookieFailedToSetSubscriptionCookie // MARK: Pixel Experiment case pixelExperimentEnrollment @@ -788,11 +793,16 @@ extension Pixel { case duckPlayerViewFromSERP case duckPlayerViewFromOther case duckPlayerOverlayYoutubeImpressions + case duckPlayerLandscapeLayoutImpressions case duckPlayerOverlayYoutubeWatchHere case duckPlayerSettingAlwaysDuckPlayer case duckPlayerSettingAlwaysSettings case duckPlayerSettingNeverSettings case duckPlayerSettingBackToDefault + case duckPlayerSettingsAlwaysOverlaySERP + case duckPlayerSettingsAlwaysOverlayYoutube + case duckPlayerSettingsNeverOverlaySERP + case duckPlayerSettingsNeverOverlayYoutube case duckPlayerWatchOnYoutube case duckPlayerSettingAlwaysOverlayYoutube case duckPlayerSettingNeverOverlayYoutube @@ -820,13 +830,9 @@ extension Pixel { case pproFeedbackSubcategoryScreenShow(source: String, reportType: String, category: String) case pproFeedbackSubmitScreenShow(source: String, reportType: String, category: String, subcategory: String) case pproFeedbackSubmitScreenFAQClick(source: String, reportType: String, category: String, subcategory: String) - - // MARK: DuckPlayer Pixel Experiment - case duckplayerExperimentCohortAssign - case duckplayerExperimentSearch - case duckplayerExperimentDailySearch - case duckplayerExperimentWeeklySearch - case duckplayerExperimentYoutubePageView + + // MARK: WebView Error Page Shown + case webViewErrorPageShown } } @@ -1517,6 +1523,11 @@ extension Pixel.Event { case .privacyProTransactionProgressNotHiddenAfter60s: return "m_privacy-pro_progress_not_hidden_after_60s" case .privacyProSuccessfulSubscriptionAttribution: return "m_subscribe" case .privacyProKeychainAccessError: return "m_privacy-pro_keychain_access_error" + case .privacyProSubscriptionCookieMissingTokenOnSignIn: return "m_privacy-pro_subscription-cookie-missing_token_on_sign_in" + case .privacyProSubscriptionCookieMissingCookieOnSignOut: return "m_privacy-pro_subscription-cookie-missing_cookie_on_sign_out" + case .privacyProSubscriptionCookieRefreshedWithAccessToken: return "m_privacy-pro_subscription-cookie-refreshed_with_access_token" + case .privacyProSubscriptionCookieRefreshedWithEmptyValue: return "m_privacy-pro_subscription-cookie-refreshed_with_empty_value" + case .privacyProSubscriptionCookieFailedToSetSubscriptionCookie: return "m_privacy-pro_subscription-cookie-failed_to_set_subscription_cookie" // MARK: Pixel Experiment case .pixelExperimentEnrollment: return "pixel_experiment_enrollment" @@ -1609,6 +1620,10 @@ extension Pixel.Event { case .duckPlayerViewFromOther: return "duckplayer_view-from_other" case .duckPlayerSettingAlwaysSettings: return "duckplayer_setting_always_settings" case .duckPlayerSettingAlwaysDuckPlayer: return "duckplayer_setting_always_duck-player" + case .duckPlayerSettingsAlwaysOverlaySERP: return "duckplayer_setting_always_overlay_serp" + case .duckPlayerSettingsAlwaysOverlayYoutube: return "duckplayer_setting_always_overlay_youtube" + case .duckPlayerSettingsNeverOverlaySERP: return "duckplayer_setting_never_overlay_serp" + case .duckPlayerSettingsNeverOverlayYoutube: return "duckplayer_setting_never_overlay_youtube" case .duckPlayerOverlayYoutubeImpressions: return "duckplayer_overlay_youtube_impressions" case .duckPlayerOverlayYoutubeWatchHere: return "duckplayer_overlay_youtube_watch_here" case .duckPlayerSettingNeverSettings: return "duckplayer_setting_never_settings" @@ -1642,13 +1657,11 @@ extension Pixel.Event { case .pproFeedbackSubmitScreenShow: return "m_ppro_feedback_submit-screen_show" case .pproFeedbackSubmitScreenFAQClick: return "m_ppro_feedback_submit-screen-faq_click" - // MARK: Duckplayer experiment - case .duckplayerExperimentCohortAssign: return "duckplayer_experiment_cohort_assign_v2" - case .duckplayerExperimentSearch: return "duckplayer_experiment_search_v2" - case .duckplayerExperimentDailySearch: return "duckplayer_experiment_daily_search_v2" - case .duckplayerExperimentWeeklySearch: return "duckplayer_experiment_weekly_search_v2" - case .duckplayerExperimentYoutubePageView: return "duckplayer_experiment_youtube_page_view_v2" - + // MARK: - WebView Error Page shown + case .webViewErrorPageShown: return "m_errorpageshown" + + // MARK: - DuckPlayer FE Application Telemetry + case .duckPlayerLandscapeLayoutImpressions: return "duckplayer_landscape_layout_impressions" } } } diff --git a/Core/SchemeHandler.swift b/Core/SchemeHandler.swift index 059e1fee45..535b0686e9 100644 --- a/Core/SchemeHandler.swift +++ b/Core/SchemeHandler.swift @@ -29,6 +29,7 @@ public class SchemeHandler { } public enum SchemeType: Equatable { + case allow case navigational case external(Action) case blob @@ -51,6 +52,7 @@ public class SchemeHandler { case shortcuts case shortcutsProduction = "shortcuts-production" case workflow + case marketplaceKit = "marketplace-kit" } private enum BlockedScheme: String { @@ -74,6 +76,13 @@ public class SchemeHandler { } switch PlatformScheme(rawValue: schemeString) { + case .marketplaceKit: + // marketplaceKit urls have to be allowed through without interference + if #available(iOS 17.4, *) { + return .allow + } else { + return .unknown + } case .sms, .mailto, .itms, .itmss, .itunes, .itmsApps, .itmsAppss, .shortcuts, .shortcutsProduction, .workflow: return .external(.askForConfirmation) case .none: diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 2e119e09df..0f625c97e3 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -171,7 +171,8 @@ public struct UserDefaultsWrapper { // Debug keys case debugNewTabPageSectionsEnabledKey = "com.duckduckgo.ios.debug.newTabPageSectionsEnabled" case debugOnboardingHighlightsEnabledKey = "com.duckduckgo.ios.debug.onboardingHighlightsEnabled" - + case debugOnboardingAddToDockEnabledKey = "com.duckduckgo.ios.debug.onboardingAddToDockEnabled" + // Duck Player Pixel Experiment case duckPlayerPixelExperimentInstalled = "com.duckduckgo.ios.duckplayer.pixel.experiment.installed.v2" case duckPlayerPixelExperimentCohort = "com.duckduckgo.ios.duckplayer.pixel.experiment.cohort.v2" @@ -180,7 +181,9 @@ public struct UserDefaultsWrapper { case duckPlayerPixelExperimentLastDayPixelFired = "com.duckduckgo.ios.duckplayer.pixel.experiment.last.day.pixel.fired.v2" case duckPlayerPixelExperimentLastVideoIDRendered = "com.duckduckgo.ios.duckplayer.pixel.experiment.last.videoID.rendered.v2" case duckPlayerPixelExperimentOverride = "com.duckduckgo.ios.duckplayer.pixel.experiment.override.v2" - + + // TipKit + case resetTipKitOnNextLaunch = "com.duckduckgo.ios.tipKit.resetOnNextLaunch" } private let key: Key diff --git a/Core/ios-config.json b/Core/ios-config.json index bd6b729093..edd16be36c 100644 --- a/Core/ios-config.json +++ b/Core/ios-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1729265730687, + "version": 1730109523334, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -360,6 +360,9 @@ { "domain": "speedweek.com" }, + { + "domain": "la-becanerie.com" + }, { "domain": "marvel.com" }, @@ -404,7 +407,7 @@ } } }, - "hash": "fba0eeeda60051f6354cf5d26c1e1a63" + "hash": "8392e127a3bcaee5c2913df355a7d254" }, "autofillBreakageReporter": { "state": "enabled", @@ -499,12 +502,15 @@ "steps": [ { "percent": 10 + }, + { + "percent": 50 } ] } } }, - "hash": "75ff21cf9a4181783c06965edd6fe746" + "hash": "9de8e4b066aa23f7c20ca638ee0d9f1a" }, "bookmarks": { "state": "enabled", @@ -1333,6 +1339,9 @@ { "domain": "flexmls.com" }, + { + "domain": "humana.com" + }, { "domain": "instructure.com" }, @@ -1340,7 +1349,12 @@ "domain": "centerwellpharmacy.com" } ], - "hash": "b4eff737bff7f262ceb567b735e1cc41" + "hash": "980bf875526f3cc7892c001a7d2e5a74" + }, + "contextualOnboarding": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" }, "cookie": { "settings": { @@ -1428,6 +1442,10 @@ { "domain": "xfinity.com", "reason": "https://github.com/duckduckgo/privacy-configuration/pull/2149" + }, + { + "domain": "ihg.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/pull/2383" } ], "ddgDefaultSites": [ @@ -1448,13 +1466,16 @@ }, { "domain": "xfinity.com" + }, + { + "domain": "ihg.com" } ], "omitVersionSites": [] }, "exceptions": [], "state": "enabled", - "hash": "2ed8c3ccd40db2d9dca1e7ecc4231045" + "hash": "e577ccb473bdb7ada49c4d3c6e79cf01" }, "dbp": { "state": "disabled", @@ -1482,7 +1503,7 @@ "state": "disabled" }, "openInNewTab": { - "state": "disabled" + "state": "internal" }, "enableDuckPlayer": { "state": "enabled", @@ -1575,7 +1596,7 @@ ] }, "state": "enabled", - "hash": "7f82d68f07b3e2aaac1b89725c1d379e" + "hash": "c21895584fc5a38e4290c7941ec7d5f8" }, "elementHiding": { "exceptions": [ @@ -5324,6 +5345,9 @@ { "domain": "dollargeneral.com" }, + { + "domain": "milesplit.live" + }, { "domain": "monsterenergy.com" }, @@ -5361,7 +5385,7 @@ "privacy-test-pages.site" ] }, - "hash": "be142a65e913cf958af67e2cd5dd8cc4" + "hash": "37630ab090682ee7d004120a42031281" }, "harmfulApis": { "settings": { @@ -8844,6 +8868,16 @@ } ] }, + "svonm.com": { + "rules": [ + { + "rule": "hgc-cf-cache-1.svonm.com", + "domains": [ + "t-online.de" + ] + } + ] + }, "taboola.com": { "rules": [ { @@ -9334,7 +9368,7 @@ "domain": "centerwellpharmacy.com" } ], - "hash": "e61f68717bcb4a465182a7ddb7d5cc4d" + "hash": "434130223ee6493827d477d0171521da" }, "trackingCookies1p": { "settings": { @@ -9605,6 +9639,11 @@ "state": "disabled", "hash": "728493ef7a1488e4781656d3f9db84aa" }, + "windowsNewTabPageExperiment": { + "state": "disabled", + "exceptions": [], + "hash": "c292bb627849854515cebbded288ef5a" + }, "windowsPermissionUsage": { "exceptions": [], "state": "disabled", diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 357751930e..7ea408820d 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -63,6 +63,7 @@ 1E1D8B6C29953CE300C96994 /* autoconsent-test-page-banner.html in Resources */ = {isa = PBXBuildFile; fileRef = 1E1D8B6929953CE300C96994 /* autoconsent-test-page-banner.html */; }; 1E24295E293F57FA00584836 /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E24295D293F57FA00584836 /* LottieView.swift */; }; 1E242960293F585300584836 /* cookie-icon-animated-40-light.json in Resources */ = {isa = PBXBuildFile; fileRef = 1E24295F293F585300584836 /* cookie-icon-animated-40-light.json */; }; + 1E39BEB02CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */; }; 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4527B6A33600961E25 /* DownloadsListViewModel.swift */; }; 1E4DCF4827B6A35400961E25 /* DownloadsListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4727B6A35400961E25 /* DownloadsListModel.swift */; }; 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E4DCF4927B6A38000961E25 /* DownloadListRepresentable.swift */; }; @@ -268,11 +269,9 @@ 560E990F2BEE2CB800507CE0 /* SyncErrorMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 560E990E2BEE2CB800507CE0 /* SyncErrorMessage.swift */; }; 564DE4532C3ED1B700D23241 /* NewTabDaxDialogFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4522C3ED1B700D23241 /* NewTabDaxDialogFactory.swift */; }; 564DE4552C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4542C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift */; }; - 564DE4572C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */; }; + 564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */; }; 564DE45A2C450BE600D23241 /* DaxDialogsNewTabTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */; }; 564DE45E2C45218500D23241 /* OnboardingNavigationDelegateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */; }; - 564DE4602C4544CA00D23241 /* HomePageDependencies.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE45F2C4544CA00D23241 /* HomePageDependencies.swift */; }; - 564DE4622C4546BE00D23241 /* HomeViewController+DaxDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 564DE4612C4546BE00D23241 /* HomeViewController+DaxDialogs.swift */; }; 566B73702BECD46800FF1959 /* MainViewController+SyncAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B736F2BECD46800FF1959 /* MainViewController+SyncAlerts.swift */; }; 566B73732BECE4F200FF1959 /* SyncErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73712BECE4F200FF1959 /* SyncErrorHandling.swift */; }; 566B73762BECE53D00FF1959 /* SyncPausedStateManaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 566B73742BECE53D00FF1959 /* SyncPausedStateManaging.swift */; }; @@ -306,7 +305,9 @@ 6F3537A42C4AC140009F8717 /* NewTabPageDaxLogoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F3537A32C4AC140009F8717 /* NewTabPageDaxLogoView.swift */; }; 6F40D15B2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F40D15A2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift */; }; 6F40D15E2C34436500BF22F0 /* HomePageDisplayDailyPixelBucketTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F40D15C2C34436200BF22F0 /* HomePageDisplayDailyPixelBucketTests.swift */; }; + 6F5041C92CC11A5100989E48 /* SimpleNewTabPageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5041C82CC11A5100989E48 /* SimpleNewTabPageView.swift */; }; 6F5345AF2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5345AE2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift */; }; + 6F5AA3EF2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5AA3EE2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift */; }; 6F5CC0812C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F5CC0802C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift */; }; 6F64AA532C47E92600CF4489 /* FavoritesFaviconLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA522C47E92600CF4489 /* FavoritesFaviconLoader.swift */; }; 6F64AA592C4818D700CF4489 /* NewTabPageShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6F64AA582C4818D700CF4489 /* NewTabPageShortcut.swift */; }; @@ -361,10 +362,19 @@ 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */; }; 6FE1274B2C20943500EB5724 /* ShortcutItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */; }; 6FEC0B852C999352006B4F6E /* FavoriteItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */; }; - 6FEC0B882C999961006B4F6E /* FavoriteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */; }; + 6FEC0B882C999961006B4F6E /* FavoritesListInteractingAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */; }; 6FF915822B88E07A0042AC87 /* AdAttributionFetcherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */; }; + 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */; }; + 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */; }; + 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */; }; + 7B8E0EC62CC81B4900B2B722 /* TipKitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */; }; 7BC571202BDBB877003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; 7BC571212BDBB977003B0CCE /* VPNActivationDateStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */; }; + 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */; }; + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */; }; + 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */; }; + 7BFD5FD72C9DB9D7000FF959 /* VPNGeoswitchingTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */; }; + 7BFD5FD92C9DBC24000FF959 /* VPNSnoozeTip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.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 */; }; @@ -396,7 +406,6 @@ 850559D023CF647C0055C0D5 /* PreserveLogins.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850559CF23CF647C0055C0D5 /* PreserveLogins.swift */; }; 850559D223CF710C0055C0D5 /* WebCacheManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 850559D123CF710C0055C0D5 /* WebCacheManagerTests.swift */; }; 85058366219AE9EA00ED4EDB /* HomePageConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85058365219AE9EA00ED4EDB /* HomePageConfiguration.swift */; }; - 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85058367219C49E000ED4EDB /* HomeViewSectionRenderers.swift */; }; 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1B745211E549D550072547E /* UIColorExtension.swift */; }; 8505836A219F424500ED4EDB /* UIAlertControllerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83004E832193E14C00DA013C /* UIAlertControllerExtension.swift */; }; 8505836C219F424500ED4EDB /* TextFieldWithInsets.swift in Sources */ = {isa = PBXBuildFile; fileRef = F197EA3B1E6885F20029BDC1 /* TextFieldWithInsets.swift */; }; @@ -452,11 +461,9 @@ 8536A1FD2ACF114B003AC5BA /* Theme+DesignSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8536A1FC2ACF114B003AC5BA /* Theme+DesignSystem.swift */; }; 85371D242121B9D500920548 /* new_tab.json in Resources */ = {isa = PBXBuildFile; fileRef = 85371D232121B9D400920548 /* new_tab.json */; }; 85372447220DD103009D09CD /* UIKeyCommandExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85372446220DD103009D09CD /* UIKeyCommandExtension.swift */; }; - 85374D3821AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85374D3721AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift */; }; 85374D3C21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85374D3B21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift */; }; 853A717620F62FE800FE60BC /* Pixel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 853A717520F62FE800FE60BC /* Pixel.swift */; }; 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 */; }; 8540BBA22440857A00017FE4 /* PreserveLoginsWorker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BBA12440857A00017FE4 /* PreserveLoginsWorker.swift */; }; 8540BD5223D8C2220057FDD2 /* PreserveLoginsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */; }; @@ -537,7 +544,6 @@ 85C29708247BDD060063A335 /* DaxDialogsBrowsingSpecTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C29706247BDCFF0063A335 /* DaxDialogsBrowsingSpecTests.swift */; }; 85C2970A247EB7AA0063A335 /* Text.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 85C29709247EB7AA0063A335 /* Text.xcassets */; }; 85C2971A248162CA0063A335 /* DaxOnboardingPadViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C29719248162CA0063A335 /* DaxOnboardingPadViewController.swift */; }; - 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C861E528FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift */; }; 85C8E61D2B0E47380029A6BD /* BookmarksDatabaseSetup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85C8E61C2B0E47380029A6BD /* BookmarksDatabaseSetup.swift */; }; 85C91CA224671F4C00A11132 /* AppDeepLinkSchemes.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17D723B1E8BB374003E8B0E /* AppDeepLinkSchemes.swift */; }; 85CA53A824BB343700A6288C /* Favicons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85CA53A324B9F2BD00A6288C /* Favicons.swift */; }; @@ -614,7 +620,6 @@ 983EABB8236198F6003948D1 /* DatabaseMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 983EABB7236198F6003948D1 /* DatabaseMigration.swift */; }; 984147A824F0259000362052 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147AA24F0259000362052 /* Onboarding.storyboard */; }; 984147AE24F0261A00362052 /* Feedback.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B024F0261A00362052 /* Feedback.storyboard */; }; - 984147B124F0264300362052 /* Home.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B324F0264300362052 /* Home.storyboard */; }; 984147B424F0264B00362052 /* Authentication.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B624F0264B00362052 /* Authentication.storyboard */; }; 984147B724F0268D00362052 /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147B924F0268D00362052 /* PrivacyDashboard.storyboard */; }; 984147C024F026A300362052 /* Tab.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 984147C224F026A300362052 /* Tab.storyboard */; }; @@ -673,7 +678,6 @@ 98B001AA251EABB40090EC07 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98B001A8251EABB40090EC07 /* Localizable.strings */; }; 98B001B0251EABB40090EC07 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98B001AE251EABB40090EC07 /* InfoPlist.strings */; }; 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 98B001B1251EABB40090EC07 /* InfoPlist.strings */; }; - 98B31290218CCB2200E54DE1 /* MockDependencyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B3128F218CCB2200E54DE1 /* MockDependencyProvider.swift */; }; 98B31292218CCB8C00E54DE1 /* AppDependencyProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98B31291218CCB8C00E54DE1 /* AppDependencyProvider.swift */; }; 98BFA911294A2086004EA636 /* bookmarks_3k.html in Resources */ = {isa = PBXBuildFile; fileRef = 98BFA910294A2086004EA636 /* bookmarks_3k.html */; }; 98BFA913294A3DDC004EA636 /* BookmarksEditModelPerformanceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 98BFA912294A3DDC004EA636 /* BookmarksEditModelPerformanceTests.swift */; }; @@ -701,6 +705,7 @@ 9F1061652C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */; }; 9F1623092C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */; }; 9F16230B2CA0F0190093C4FC /* DebouncerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */; }; + 9F1798572CD2443F0073018B /* AddToDockPromoViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */; }; 9F23B8012C2BC94400950875 /* OnboardingBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */; }; 9F23B8032C2BCD0000950875 /* DaxDialogStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */; }; 9F23B8062C2BE22700950875 /* OnboardingIntroViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */; }; @@ -721,11 +726,20 @@ 9F69331D2C5A191400CD6A5D /* MockTutorialSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F69331C2C5A191400CD6A5D /* MockTutorialSettings.swift */; }; 9F69331F2C5B1D0C00CD6A5D /* OnFirstAppearViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F69331E2C5B1D0C00CD6A5D /* OnFirstAppearViewModifier.swift */; }; 9F6933212C5B9A5B00CD6A5D /* OnboardingHostingControllerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F6933202C5B9A5B00CD6A5D /* OnboardingHostingControllerMock.swift */; }; + 9F72FE272CD223A000BA35F5 /* add-to-dock-promo.json in Resources */ = {isa = PBXBuildFile; fileRef = 9F72FE262CD223A000BA35F5 /* add-to-dock-promo.json */; }; 9F7CFF762C86BB8F0012833E /* OnboardingView+AppIconPickerContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7CFF752C86BB8F0012833E /* OnboardingView+AppIconPickerContent.swift */; }; 9F7CFF782C86E3E10012833E /* OnboardingManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7CFF772C86E3E10012833E /* OnboardingManagerTests.swift */; }; 9F7CFF7D2C89B69A0012833E /* AppIconPickerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7CFF7C2C89B69A0012833E /* AppIconPickerViewModelTests.swift */; }; 9F7CFF7F2C8A94F70012833E /* OnboardingView+AddressBarPositionContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F7CFF7E2C8A94F70012833E /* OnboardingView+AddressBarPositionContent.swift */; }; 9F8007262C5261AF003EDAF4 /* MockPrivacyDataReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8007252C5261AF003EDAF4 /* MockPrivacyDataReporter.swift */; }; + 9F8E0F262CC9395D001EA7C5 /* Logger+Onboarding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F252CC9395D001EA7C5 /* Logger+Onboarding.swift */; }; + 9F8E0F2A2CCA5C9D001EA7C5 /* add-to-dock-demo.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = 9F8E0F292CCA5C9D001EA7C5 /* add-to-dock-demo.mp4 */; }; + 9F8E0F2D2CCA618E001EA7C5 /* VideoPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F2C2CCA618E001EA7C5 /* VideoPlayerView.swift */; }; + 9F8E0F2F2CCA6202001EA7C5 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F2E2CCA6202001EA7C5 /* VideoPlayerViewModel.swift */; }; + 9F8E0F312CCA6390001EA7C5 /* AddToDockTutorialView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F302CCA6390001EA7C5 /* AddToDockTutorialView.swift */; }; + 9F8E0F332CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F322CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift */; }; + 9F8E0F382CCFAA8A001EA7C5 /* AddToDockPromoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F372CCFAA8A001EA7C5 /* AddToDockPromoView.swift */; }; + 9F8E0F3D2CCFD072001EA7C5 /* AddToDockPromoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F8E0F3C2CCFD071001EA7C5 /* AddToDockPromoViewModel.swift */; }; 9F8FE9492BAE50E50071E372 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 9F8FE9482BAE50E50071E372 /* Lottie */; }; 9F96F73B2C9144D5009E45D5 /* Onboarding in Frameworks */ = {isa = PBXBuildFile; productRef = 9F96F73A2C9144D5009E45D5 /* Onboarding */; }; 9F96F73F2C914C57009E45D5 /* OnboardingGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F96F73E2C914C57009E45D5 /* OnboardingGradient.swift */; }; @@ -954,7 +968,6 @@ D60170BD2BA34CE8001911B5 /* Subscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60170BB2BA32DD6001911B5 /* Subscription.swift */; }; D6037E692C32F2E7009AAEC0 /* DuckPlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6037E682C32F2E7009AAEC0 /* DuckPlayerSettings.swift */; }; D60B1F272B9DDE5A00AE4760 /* SubscriptionGoogleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */; }; - D60E5C2F2C862297007D6BC7 /* DuckPlayerLaunchExperiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D60E5C2E2C862297007D6BC7 /* DuckPlayerLaunchExperiment.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 */; }; @@ -1010,7 +1023,6 @@ D6E83C602B22B3C9006C8AFB /* SettingsState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */; }; D6E83C662B23936F006C8AFB /* SettingsDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */; }; D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6E83C672B23B6A3006C8AFB /* FontSettings.swift */; }; - D6F557BA2C8859040034444B /* DuckPlayerExperimentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D6F557B92C8859040034444B /* DuckPlayerExperimentTests.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 */; }; @@ -1092,7 +1104,6 @@ F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */; }; F1617C151E57336D00DEDCAF /* TabManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C141E57336D00DEDCAF /* TabManager.swift */; }; F1617C191E573EA800DEDCAF /* TabSwitcherDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */; }; - F16390821E648B7A005B4550 /* HomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16390811E648B7A005B4550 /* HomeViewController.swift */; }; F16393FF1ECCB9CC00DDD653 /* FileLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F16393FE1ECCB9CC00DDD653 /* FileLoader.swift */; }; F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1668BCD1E798081008CBA04 /* BookmarksViewController.swift */; }; F176699F1E40BC86003D3222 /* Settings.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F176699D1E40BC86003D3222 /* Settings.storyboard */; }; @@ -1131,7 +1142,6 @@ F1DE78581E5CAE350058895A /* TabViewGridCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1DE78571E5CAE350058895A /* TabViewGridCell.swift */; }; F1E092C11E92A72E00732CCC /* UIColorExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E092C01E92A72E00732CCC /* UIColorExtensionTests.swift */; }; F1E4A4451EE89460006F2EAE /* Bookmarks.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F1E4A4431EE89460006F2EAE /* Bookmarks.storyboard */; }; - F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1E90C1F1E678E7C005E7E21 /* HomeControllerDelegate.swift */; }; F1ED309D1EDC2EA400651986 /* TabSwitcher.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F1ED309B1EDC2EA400651986 /* TabSwitcher.storyboard */; }; F1EFB0062C5B8B8E009AB44B /* StatusIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1EFB0052C5B8B8E009AB44B /* StatusIndicatorView.swift */; }; F1F5337C1F26A9EF00D80D4F /* UserText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F1F5337B1F26A9EF00D80D4F /* UserText.swift */; }; @@ -1147,7 +1157,6 @@ F41C2DA526C1975E00F9A760 /* LegacyBookmarksCoreDataStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = F41C2DA426C1975E00F9A760 /* LegacyBookmarksCoreDataStorage.swift */; }; F42D541D29DCA40B004C4FF1 /* DesignResourcesKit in Frameworks */ = {isa = PBXBuildFile; productRef = F42D541C29DCA40B004C4FF1 /* DesignResourcesKit */; }; F42EF9312614BABE00101FB9 /* ActionSheetDaxDialogViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F42EF9302614BABD00101FB9 /* ActionSheetDaxDialogViewController.swift */; }; - F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = F446B9B4251150AC00324016 /* HomeMessageViewSectionRenderer.swift */; }; F44D279C27F331BB0037F371 /* AutofillLoginPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */; }; F44D279E27F331BB0037F371 /* AutofillLoginPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */; }; F44D279F27F331BB0037F371 /* AutofillLoginPromptViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */; }; @@ -1368,6 +1377,7 @@ 1E24295D293F57FA00584836 /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = ""; }; 1E24295F293F585300584836 /* cookie-icon-animated-40-light.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "cookie-icon-animated-40-light.json"; sourceTree = ""; }; 1E25D5312C92126B004400F0 /* SubscriptionFeatureAvailabilityMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionFeatureAvailabilityMock.swift; sourceTree = ""; }; + 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionCookieManageEventPixelMapping.swift; sourceTree = ""; }; 1E4DCF4527B6A33600961E25 /* DownloadsListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListViewModel.swift; sourceTree = ""; }; 1E4DCF4727B6A35400961E25 /* DownloadsListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsListModel.swift; sourceTree = ""; }; 1E4DCF4927B6A38000961E25 /* DownloadListRepresentable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadListRepresentable.swift; sourceTree = ""; }; @@ -1552,11 +1562,9 @@ 560E990E2BEE2CB800507CE0 /* SyncErrorMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorMessage.swift; sourceTree = ""; }; 564DE4522C3ED1B700D23241 /* NewTabDaxDialogFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabDaxDialogFactory.swift; sourceTree = ""; }; 564DE4542C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextualOnboardingNewTabDialogFactoryTests.swift; sourceTree = ""; }; - 564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewControllerDaxDialogTests.swift; sourceTree = ""; }; + 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageControllerDaxDialogTests.swift; sourceTree = ""; }; 564DE4592C450BE600D23241 /* DaxDialogsNewTabTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogsNewTabTests.swift; sourceTree = ""; }; 564DE45D2C45218500D23241 /* OnboardingNavigationDelegateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingNavigationDelegateTests.swift; sourceTree = ""; }; - 564DE45F2C4544CA00D23241 /* HomePageDependencies.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageDependencies.swift; sourceTree = ""; }; - 564DE4612C4546BE00D23241 /* HomeViewController+DaxDialogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "HomeViewController+DaxDialogs.swift"; sourceTree = ""; }; 566B736F2BECD46800FF1959 /* MainViewController+SyncAlerts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+SyncAlerts.swift"; sourceTree = ""; }; 566B73712BECE4F200FF1959 /* SyncErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncErrorHandling.swift; sourceTree = ""; }; 566B73742BECE53D00FF1959 /* SyncPausedStateManaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SyncPausedStateManaging.swift; sourceTree = ""; }; @@ -1590,7 +1598,9 @@ 6F3537A32C4AC140009F8717 /* NewTabPageDaxLogoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageDaxLogoView.swift; sourceTree = ""; }; 6F40D15A2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageDisplayDailyPixelBucket.swift; sourceTree = ""; }; 6F40D15C2C34436200BF22F0 /* HomePageDisplayDailyPixelBucketTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageDisplayDailyPixelBucketTests.swift; sourceTree = ""; }; + 6F5041C82CC11A5100989E48 /* SimpleNewTabPageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleNewTabPageView.swift; sourceTree = ""; }; 6F5345AE2C53F2DE00424A43 /* NewTabPageSettingsPersistentStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageSettingsPersistentStorage.swift; sourceTree = ""; }; + 6F5AA3EE2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapterTests.swift; sourceTree = ""; }; 6F5CC0802C2AFFE400AFC840 /* ToggleExpandButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleExpandButtonStyle.swift; sourceTree = ""; }; 6F64AA522C47E92600CF4489 /* FavoritesFaviconLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesFaviconLoader.swift; sourceTree = ""; }; 6F64AA582C4818D700CF4489 /* NewTabPageShortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageShortcut.swift; sourceTree = ""; }; @@ -1646,9 +1656,18 @@ 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewTabPageViewController.swift; sourceTree = ""; }; 6FE1274A2C20943500EB5724 /* ShortcutItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutItemView.swift; sourceTree = ""; }; 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteItem.swift; sourceTree = ""; }; - 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoriteDataSource.swift; sourceTree = ""; }; + 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesListInteractingAdapter.swift; sourceTree = ""; }; 6FF915802B88E0750042AC87 /* AdAttributionFetcherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdAttributionFetcherTests.swift; sourceTree = ""; }; + 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+TipKit.swift"; sourceTree = ""; }; + 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TipKitController+ConvenienceInitializers.swift"; sourceTree = ""; }; + 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitDebugOptionsUIActionHandling.swift; sourceTree = ""; }; + 7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitController.swift; sourceTree = ""; }; 7BC5711F2BDBB877003B0CCE /* VPNActivationDateStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNActivationDateStore.swift; sourceTree = ""; }; + 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = VPN.xcassets; sourceTree = ""; }; + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipKitAppEventHandling.swift; sourceTree = ""; }; + 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNAddWidgetTip.swift; sourceTree = ""; }; + 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNGeoswitchingTip.swift; sourceTree = ""; }; + 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNSnoozeTip.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 = ""; }; @@ -1695,7 +1714,6 @@ 850559CF23CF647C0055C0D5 /* PreserveLogins.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreserveLogins.swift; sourceTree = ""; }; 850559D123CF710C0055C0D5 /* WebCacheManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebCacheManagerTests.swift; sourceTree = ""; }; 85058365219AE9EA00ED4EDB /* HomePageConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomePageConfiguration.swift; sourceTree = ""; }; - 85058367219C49E000ED4EDB /* HomeViewSectionRenderers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewSectionRenderers.swift; sourceTree = ""; }; 850ABD002AC3961100A733DF /* MainViewController+Segues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MainViewController+Segues.swift"; sourceTree = ""; }; 850ABD022AC4D46C00A733DF /* SuggestionTray.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = SuggestionTray.storyboard; sourceTree = ""; }; 850F93DA2B594AB800823EEA /* ZippedPassKitPreviewHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZippedPassKitPreviewHelper.swift; sourceTree = ""; }; @@ -1741,11 +1759,9 @@ 8536A1FC2ACF114B003AC5BA /* Theme+DesignSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Theme+DesignSystem.swift"; sourceTree = ""; }; 85371D232121B9D400920548 /* new_tab.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = new_tab.json; sourceTree = ""; }; 85372446220DD103009D09CD /* UIKeyCommandExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKeyCommandExtension.swift; sourceTree = ""; }; - 85374D3721AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationSearchHomeViewSectionRenderer.swift; sourceTree = ""; }; 85374D3B21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FavoritesHomeViewSectionRenderer.swift; sourceTree = ""; }; 853A717520F62FE800FE60BC /* Pixel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Pixel.swift; sourceTree = ""; }; 853A717720F645FB00FE60BC /* PixelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PixelTests.swift; sourceTree = ""; }; - 853C5F5A21BFF0AE001F7A05 /* HomeCollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeCollectionView.swift; sourceTree = ""; }; 853C5F6021C277C7001F7A05 /* global.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = global.swift; sourceTree = ""; }; 8540BBA12440857A00017FE4 /* PreserveLoginsWorker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreserveLoginsWorker.swift; sourceTree = ""; }; 8540BD5123D8C2220057FDD2 /* PreserveLoginsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreserveLoginsTests.swift; sourceTree = ""; }; @@ -1828,7 +1844,6 @@ 85C29706247BDCFF0063A335 /* DaxDialogsBrowsingSpecTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogsBrowsingSpecTests.swift; sourceTree = ""; }; 85C29709247EB7AA0063A335 /* Text.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Text.xcassets; sourceTree = ""; }; 85C29719248162CA0063A335 /* DaxOnboardingPadViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxOnboardingPadViewController.swift; sourceTree = ""; }; - 85C861E528FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeViewSectionRenderersExtension.swift; sourceTree = ""; }; 85C8E61C2B0E47380029A6BD /* BookmarksDatabaseSetup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BookmarksDatabaseSetup.swift; sourceTree = ""; }; 85CA53A324B9F2BD00A6288C /* Favicons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = Favicons.swift; path = ../DuckDuckGo/Favicons.swift; sourceTree = ""; }; 85CA53A924BB376800A6288C /* NotFoundCachingDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotFoundCachingDownloader.swift; sourceTree = ""; }; @@ -1896,7 +1911,6 @@ 9813F79722BA71AA00A80EDB /* StorageCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StorageCache.swift; sourceTree = ""; }; 981685442521EEEF00FA91A1 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Bookmarks.strings; sourceTree = ""; }; 981685452521EEF000FA91A1 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Feedback.strings; sourceTree = ""; }; - 981685462521EEF000FA91A1 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Home.strings; sourceTree = ""; }; 981685482521EEF100FA91A1 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/OmniBar.strings; sourceTree = ""; }; 981685492521EEF100FA91A1 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Authentication.strings; sourceTree = ""; }; 9816854C2521EEF200FA91A1 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/Settings.strings; sourceTree = ""; }; @@ -1964,7 +1978,6 @@ 983EABB7236198F6003948D1 /* DatabaseMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseMigration.swift; sourceTree = ""; }; 984147A924F0259000362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Onboarding.storyboard; sourceTree = ""; }; 984147AF24F0261A00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Feedback.storyboard; sourceTree = ""; }; - 984147B224F0264300362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Home.storyboard; sourceTree = ""; }; 984147B524F0264B00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Authentication.storyboard; sourceTree = ""; }; 984147B824F0268D00362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/PrivacyDashboard.storyboard; sourceTree = ""; }; 984147C124F026A300362052 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Tab.storyboard; sourceTree = ""; }; @@ -2003,7 +2016,6 @@ 9865DFFC22A84CF300D27829 /* FavoriteHomeCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = FavoriteHomeCell.xib; sourceTree = ""; }; 9866DB8C251CA8F300612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DB8D251CA8F300612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Feedback.strings; sourceTree = ""; }; - 9866DB8E251CA8F400612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Home.strings; sourceTree = ""; }; 9866DB90251CA8F400612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/OmniBar.strings; sourceTree = ""; }; 9866DB91251CA8F400612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Authentication.strings; sourceTree = ""; }; 9866DB94251CA8F500612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/Settings.strings; sourceTree = ""; }; @@ -2019,7 +2031,6 @@ 9866DBA1251CA8F700612E3A /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DBA3251CA91700612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DBA4251CA91700612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Feedback.strings; sourceTree = ""; }; - 9866DBA5251CA91800612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Home.strings; sourceTree = ""; }; 9866DBA7251CA91800612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/OmniBar.strings; sourceTree = ""; }; 9866DBA8251CA91800612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Authentication.strings; sourceTree = ""; }; 9866DBAB251CA91800612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Settings.strings; sourceTree = ""; }; @@ -2035,7 +2046,6 @@ 9866DBB8251CA91900612E3A /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DBBA251CA92A00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DBBB251CA92A00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Feedback.strings; sourceTree = ""; }; - 9866DBBC251CA92A00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Home.strings; sourceTree = ""; }; 9866DBBE251CA92B00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/OmniBar.strings; sourceTree = ""; }; 9866DBBF251CA92B00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Authentication.strings; sourceTree = ""; }; 9866DBC2251CA92B00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Settings.strings; sourceTree = ""; }; @@ -2051,7 +2061,6 @@ 9866DBCF251CA92E00612E3A /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DBD1251CA93800612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DBD2251CA93900612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Feedback.strings; sourceTree = ""; }; - 9866DBD3251CA93900612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Home.strings; sourceTree = ""; }; 9866DBD5251CA93900612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/OmniBar.strings; sourceTree = ""; }; 9866DBD6251CA93900612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Authentication.strings; sourceTree = ""; }; 9866DBD9251CA93A00612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Settings.strings; sourceTree = ""; }; @@ -2067,7 +2076,6 @@ 9866DBE6251CA93B00612E3A /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DBE8251CA94E00612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DBE9251CA94E00612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Feedback.strings; sourceTree = ""; }; - 9866DBEA251CA94F00612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Home.strings; sourceTree = ""; }; 9866DBEC251CA94F00612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/OmniBar.strings; sourceTree = ""; }; 9866DBED251CA94F00612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Authentication.strings; sourceTree = ""; }; 9866DBF0251CA95000612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Settings.strings; sourceTree = ""; }; @@ -2083,7 +2091,6 @@ 9866DBFD251CA95200612E3A /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DBFF251CA96200612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC00251CA96200612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC01251CA96200612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Home.strings; sourceTree = ""; }; 9866DC03251CA96300612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC04251CA96300612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Authentication.strings; sourceTree = ""; }; 9866DC07251CA96300612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Settings.strings; sourceTree = ""; }; @@ -2099,7 +2106,6 @@ 9866DC14251CA96500612E3A /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DC16251CA99A00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC17251CA99B00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC18251CA99B00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Home.strings; sourceTree = ""; }; 9866DC1A251CA99B00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC1B251CA99B00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Authentication.strings; sourceTree = ""; }; 9866DC1E251CA99C00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Settings.strings; sourceTree = ""; }; @@ -2115,7 +2121,6 @@ 9866DC2B251CA99E00612E3A /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DC2D251CA9B000612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC2E251CA9B000612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC2F251CA9B000612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Home.strings; sourceTree = ""; }; 9866DC31251CA9B000612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC32251CA9B000612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Authentication.strings; sourceTree = ""; }; 9866DC35251CA9B100612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Settings.strings; sourceTree = ""; }; @@ -2131,7 +2136,6 @@ 9866DC42251CA9B200612E3A /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DC44251CA9BF00612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC45251CA9BF00612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC46251CA9C000612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Home.strings; sourceTree = ""; }; 9866DC48251CA9C000612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC49251CA9C000612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Authentication.strings; sourceTree = ""; }; 9866DC4C251CA9C100612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Settings.strings; sourceTree = ""; }; @@ -2147,7 +2151,6 @@ 9866DC59251CA9C300612E3A /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DC5B251CA9CE00612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC5C251CA9CE00612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC5D251CA9CE00612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Home.strings; sourceTree = ""; }; 9866DC5F251CA9CF00612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC60251CA9CF00612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Authentication.strings; sourceTree = ""; }; 9866DC63251CA9CF00612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Settings.strings; sourceTree = ""; }; @@ -2163,7 +2166,6 @@ 9866DC70251CA9D100612E3A /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DC72251CA9E200612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC73251CA9E300612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC74251CA9E300612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Home.strings; sourceTree = ""; }; 9866DC76251CA9E300612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC77251CA9E300612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Authentication.strings; sourceTree = ""; }; 9866DC7A251CA9E400612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/Settings.strings; sourceTree = ""; }; @@ -2179,7 +2181,6 @@ 9866DC87251CA9E600612E3A /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DC89251CA9F500612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DC8A251CA9F500612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Feedback.strings; sourceTree = ""; }; - 9866DC8B251CA9F500612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Home.strings; sourceTree = ""; }; 9866DC8D251CA9F500612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/OmniBar.strings; sourceTree = ""; }; 9866DC8E251CA9F500612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Authentication.strings; sourceTree = ""; }; 9866DC91251CA9F600612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/Settings.strings; sourceTree = ""; }; @@ -2195,7 +2196,6 @@ 9866DC9E251CA9F700612E3A /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DCA0251CAA0500612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DCA1251CAA0500612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Feedback.strings; sourceTree = ""; }; - 9866DCA2251CAA0500612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Home.strings; sourceTree = ""; }; 9866DCA4251CAA0500612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/OmniBar.strings; sourceTree = ""; }; 9866DCA5251CAA0500612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Authentication.strings; sourceTree = ""; }; 9866DCA8251CAA0600612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/Settings.strings; sourceTree = ""; }; @@ -2211,7 +2211,6 @@ 9866DCB5251CAA0900612E3A /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DCB7251CAA2600612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DCB8251CAA2600612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Feedback.strings; sourceTree = ""; }; - 9866DCB9251CAA2600612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Home.strings; sourceTree = ""; }; 9866DCBB251CAA2600612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/OmniBar.strings; sourceTree = ""; }; 9866DCBC251CAA2700612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Authentication.strings; sourceTree = ""; }; 9866DCBF251CAA2700612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/Settings.strings; sourceTree = ""; }; @@ -2227,7 +2226,6 @@ 9866DCCC251CAA2800612E3A /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DCCE251CAA3300612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DCCF251CAA3400612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Feedback.strings; sourceTree = ""; }; - 9866DCD0251CAA3400612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Home.strings; sourceTree = ""; }; 9866DCD2251CAA3400612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/OmniBar.strings; sourceTree = ""; }; 9866DCD3251CAA3400612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Authentication.strings; sourceTree = ""; }; 9866DCD6251CAA3500612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Settings.strings; sourceTree = ""; }; @@ -2243,7 +2241,6 @@ 9866DCE3251CAA3700612E3A /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = ""; }; 9866DCE5251CAA4800612E3A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Bookmarks.strings; sourceTree = ""; }; 9866DCE6251CAA4800612E3A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Feedback.strings; sourceTree = ""; }; - 9866DCE7251CAA4800612E3A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Home.strings; sourceTree = ""; }; 9866DCE9251CAA4900612E3A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/OmniBar.strings; sourceTree = ""; }; 9866DCEA251CAA4900612E3A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Authentication.strings; sourceTree = ""; }; 9866DCED251CAA4900612E3A /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/Settings.strings; sourceTree = ""; }; @@ -2278,13 +2275,6 @@ 9866DD56251CB0F000612E3A /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Feedback.strings; sourceTree = ""; }; 9866DD58251CB0F100612E3A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Feedback.strings; sourceTree = ""; }; 9866DD5A251CB0F200612E3A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Feedback.strings; sourceTree = ""; }; - 9866DD5C251CB10000612E3A /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Home.strings; sourceTree = ""; }; - 9866DD5E251CB10100612E3A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Home.strings; sourceTree = ""; }; - 9866DD62251CB10400612E3A /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Home.strings; sourceTree = ""; }; - 9866DD64251CB10500612E3A /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Home.strings; sourceTree = ""; }; - 9866DD66251CB10600612E3A /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Home.strings; sourceTree = ""; }; - 9866DD68251CB10700612E3A /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Home.strings; sourceTree = ""; }; - 9866DD6A251CB10800612E3A /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Home.strings; sourceTree = ""; }; 9866DD84251CB12700612E3A /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/Authentication.strings; sourceTree = ""; }; 9866DD86251CB12800612E3A /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Authentication.strings; sourceTree = ""; }; 9866DD8A251CB12B00612E3A /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/Authentication.strings; sourceTree = ""; }; @@ -2454,7 +2444,6 @@ 98B12739251EABD5007473E4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; 98B1273B251EABD5007473E4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; 98B1273C251EABD5007473E4 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Localizable.strings; sourceTree = ""; }; - 98B3128F218CCB2200E54DE1 /* MockDependencyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockDependencyProvider.swift; sourceTree = ""; }; 98B31291218CCB8C00E54DE1 /* AppDependencyProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDependencyProvider.swift; sourceTree = ""; }; 98B4904D251EAC2200A1B398 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; 98B4904E251EAC2200A1B398 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2514,6 +2503,7 @@ 9F1061642C9C013F008DD5A0 /* DefaultVariantManager+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "DefaultVariantManager+Onboarding.swift"; sourceTree = ""; }; 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultVariantManagerOnboardingTests.swift; sourceTree = ""; }; 9F16230A2CA0F0190093C4FC /* DebouncerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebouncerTests.swift; sourceTree = ""; }; + 9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockPromoViewModelTests.swift; sourceTree = ""; }; 9F23B8002C2BC94400950875 /* OnboardingBackground.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackground.swift; sourceTree = ""; }; 9F23B8022C2BCD0000950875 /* DaxDialogStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxDialogStyles.swift; sourceTree = ""; }; 9F23B8052C2BE22700950875 /* OnboardingIntroViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingIntroViewModelTests.swift; sourceTree = ""; }; @@ -2534,11 +2524,20 @@ 9F69331C2C5A191400CD6A5D /* MockTutorialSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTutorialSettings.swift; sourceTree = ""; }; 9F69331E2C5B1D0C00CD6A5D /* OnFirstAppearViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnFirstAppearViewModifier.swift; sourceTree = ""; }; 9F6933202C5B9A5B00CD6A5D /* OnboardingHostingControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingHostingControllerMock.swift; sourceTree = ""; }; + 9F72FE262CD223A000BA35F5 /* add-to-dock-promo.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "add-to-dock-promo.json"; sourceTree = ""; }; 9F7CFF752C86BB8F0012833E /* OnboardingView+AppIconPickerContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+AppIconPickerContent.swift"; sourceTree = ""; }; 9F7CFF772C86E3E10012833E /* OnboardingManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingManagerTests.swift; sourceTree = ""; }; 9F7CFF7C2C89B69A0012833E /* AppIconPickerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIconPickerViewModelTests.swift; sourceTree = ""; }; 9F7CFF7E2C8A94F70012833E /* OnboardingView+AddressBarPositionContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnboardingView+AddressBarPositionContent.swift"; sourceTree = ""; }; 9F8007252C5261AF003EDAF4 /* MockPrivacyDataReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockPrivacyDataReporter.swift; sourceTree = ""; }; + 9F8E0F252CC9395D001EA7C5 /* Logger+Onboarding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Onboarding.swift"; sourceTree = ""; }; + 9F8E0F292CCA5C9D001EA7C5 /* add-to-dock-demo.mp4 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "add-to-dock-demo.mp4"; sourceTree = ""; }; + 9F8E0F2C2CCA618E001EA7C5 /* VideoPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerView.swift; sourceTree = ""; }; + 9F8E0F2E2CCA6202001EA7C5 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = ""; }; + 9F8E0F302CCA6390001EA7C5 /* AddToDockTutorialView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockTutorialView.swift; sourceTree = ""; }; + 9F8E0F322CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModelTests.swift; sourceTree = ""; }; + 9F8E0F372CCFAA8A001EA7C5 /* AddToDockPromoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockPromoView.swift; sourceTree = ""; }; + 9F8E0F3C2CCFD071001EA7C5 /* AddToDockPromoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddToDockPromoViewModel.swift; sourceTree = ""; }; 9F96F73E2C914C57009E45D5 /* OnboardingGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingGradient.swift; sourceTree = ""; }; 9F9A922D2C86A56B001D036D /* OnboardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingManager.swift; sourceTree = ""; }; 9F9A92302C86AAE9001D036D /* OnboardingDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingDebugView.swift; sourceTree = ""; }; @@ -2727,7 +2726,6 @@ CB18F2712AF6D4E400A0F8FE /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/InfoPlist.strings; sourceTree = ""; }; CB1AEFB02799AA940031AE3D /* SwiftUICollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUICollectionViewCell.swift; sourceTree = ""; }; CB1FAE472AF6D59B003F452F /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/InfoPlist.strings; sourceTree = ""; }; - CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptView.swift; sourceTree = ""; }; CB24F70E29A3EB15006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppConfigurationURLProvider.swift; path = ../Core/AppConfigurationURLProvider.swift; sourceTree = ""; }; CB258D0C29A4CD0500DEBA24 /* Configuration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Configuration.swift; sourceTree = ""; }; CB258D0F29A4D0FD00DEBA24 /* ConfigurationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationManager.swift; sourceTree = ""; }; @@ -2741,7 +2739,6 @@ CB48D3352B90CECD00631D8B /* UserBehaviorMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserBehaviorMonitorTests.swift; sourceTree = ""; }; CB4FA44D2C78AACE00A16F5A /* SpecialErrorPageUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpecialErrorPageUserScript.swift; sourceTree = ""; }; CB5038622AF6D563007FD69F /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/InfoPlist.strings; sourceTree = ""; }; - CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrokenSitePromptViewModel.swift; 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 = ""; }; CB7407BC2AF6D56D0090A41C /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/InfoPlist.strings; sourceTree = ""; }; @@ -2780,7 +2777,6 @@ D60170BB2BA32DD6001911B5 /* Subscription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Subscription.swift; sourceTree = ""; }; D6037E682C32F2E7009AAEC0 /* DuckPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerSettings.swift; sourceTree = ""; }; D60B1F262B9DDE5A00AE4760 /* SubscriptionGoogleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionGoogleView.swift; sourceTree = ""; }; - D60E5C2E2C862297007D6BC7 /* DuckPlayerLaunchExperiment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerLaunchExperiment.swift; sourceTree = ""; }; D625AAEA2BBEEFC900BC189A /* TabURLInterceptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabURLInterceptorTests.swift; sourceTree = ""; }; D62EC3B82C246A5600FC9D04 /* YoutublePlayerNavigationHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YoutublePlayerNavigationHandlerTests.swift; sourceTree = ""; }; D62EC3BB2C2470E000FC9D04 /* DuckPlayerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerTests.swift; sourceTree = ""; }; @@ -2832,7 +2828,6 @@ D6E83C5F2B22B3C9006C8AFB /* SettingsState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsState.swift; sourceTree = ""; }; D6E83C652B23936F006C8AFB /* SettingsDebugView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDebugView.swift; sourceTree = ""; }; D6E83C672B23B6A3006C8AFB /* FontSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontSettings.swift; sourceTree = ""; }; - D6F557B92C8859040034444B /* DuckPlayerExperimentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerExperimentTests.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 = ""; }; @@ -2939,7 +2934,6 @@ F1617C121E572E0300DEDCAF /* TabSwitcherViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherViewController.swift; sourceTree = ""; }; F1617C141E57336D00DEDCAF /* TabManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabManager.swift; sourceTree = ""; }; F1617C181E573EA800DEDCAF /* TabSwitcherDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabSwitcherDelegate.swift; sourceTree = ""; }; - F16390811E648B7A005B4550 /* HomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeViewController.swift; sourceTree = ""; }; F16393F41ECCA85900DDD653 /* DomainsProtectionUserDefaultsStoreTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DomainsProtectionUserDefaultsStoreTests.swift; sourceTree = ""; }; F16393FE1ECCB9CC00DDD653 /* FileLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileLoader.swift; sourceTree = ""; }; F1668BCD1E798081008CBA04 /* BookmarksViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksViewController.swift; sourceTree = ""; }; @@ -2983,7 +2977,6 @@ F1DE78591E5CD2A70058895A /* UIViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = UIViewExtension.swift; path = ../Core/UIViewExtension.swift; sourceTree = ""; }; F1E092C01E92A72E00732CCC /* UIColorExtensionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColorExtensionTests.swift; sourceTree = ""; }; F1E4A4441EE89460006F2EAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Bookmarks.storyboard; sourceTree = ""; }; - F1E90C1F1E678E7C005E7E21 /* HomeControllerDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeControllerDelegate.swift; sourceTree = ""; }; F1ED309C1EDC2EA400651986 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/TabSwitcher.storyboard; sourceTree = ""; }; F1EFB0052C5B8B8E009AB44B /* StatusIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusIndicatorView.swift; sourceTree = ""; }; F1F5337B1F26A9EF00D80D4F /* UserText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserText.swift; sourceTree = ""; }; @@ -2995,7 +2988,6 @@ F41C2DA226C1925700F9A760 /* BookmarksAndFolders.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = BookmarksAndFolders.xcdatamodel; sourceTree = ""; }; F41C2DA426C1975E00F9A760 /* LegacyBookmarksCoreDataStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyBookmarksCoreDataStorage.swift; sourceTree = ""; }; F42EF9302614BABD00101FB9 /* ActionSheetDaxDialogViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetDaxDialogViewController.swift; sourceTree = ""; }; - F446B9B4251150AC00324016 /* HomeMessageViewSectionRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeMessageViewSectionRenderer.swift; sourceTree = ""; }; F44D279727F331BB0037F371 /* AutofillLoginPromptView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptView.swift; sourceTree = ""; }; F44D279927F331BB0037F371 /* AutofillLoginPromptViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewModel.swift; sourceTree = ""; }; F44D279A27F331BB0037F371 /* AutofillLoginPromptViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutofillLoginPromptViewController.swift; sourceTree = ""; }; @@ -3193,15 +3185,6 @@ path = FingerprintingUITests; sourceTree = ""; }; - 0283A1F82C6E3C3100508FBD /* Recovered References */ = { - isa = PBXGroup; - children = ( - CB2283F22BD79FC20057DD0A /* BrokenSitePromptView.swift */, - CB5418622BD90CD000C2CD26 /* BrokenSitePromptViewModel.swift */, - ); - name = "Recovered References"; - sourceTree = ""; - }; 0283A1F92C6E3D4200508FBD /* BrokenSitePrompt */ = { isa = PBXGroup; children = ( @@ -3737,6 +3720,7 @@ children = ( 4B37E04F2B928CA6009E81CA /* vpn-light-mode.json */, 4B6ED9442B992FE4007F5CAA /* vpn-dark-mode.json */, + 7BDBAD0D2CBFB3F1000379B7 /* VPN.xcassets */, ); name = Resources; sourceTree = ""; @@ -3854,12 +3838,22 @@ 6F7FB8DF2C660B1A00867DA7 /* NewTabPageFavoritesModelTests.swift */, 6F7FB8E42C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift */, 6F7FB8E62C66197E00867DA7 /* NewTabPageSectionsSettingsModelTests.swift */, - 564DE4562C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift */, + 564DE4562C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift */, 6FABAA682C6116FD003762EC /* NewTabPageShortcutsSettingsStorageTests.swift */, + 6F5AA3EE2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift */, ); name = NewTabPage; sourceTree = ""; }; + 6F32EEC32CC2B6B2007F3DA2 /* FavoritesSearchOverlay */ = { + isa = PBXGroup; + children = ( + 85B9CB8321AEBD72009001F1 /* Cells */, + 85374D3621AC417200FF5A1E /* Renderers */, + ); + name = FavoritesSearchOverlay; + sourceTree = ""; + }; 6F35379C2C4AAF1C009F8717 /* Settings */ = { isa = PBXGroup; children = ( @@ -3909,7 +3903,7 @@ 6FD3F8122C3EFDA200DA5797 /* FavoritesPreviewDataSource.swift */, 6FA3438E2C3D3BC300470677 /* Favorite.swift */, 6FEC0B842C999352006B4F6E /* FavoriteItem.swift */, - 6FEC0B872C999961006B4F6E /* FavoriteDataSource.swift */, + 6FEC0B872C999961006B4F6E /* FavoritesListInteractingAdapter.swift */, ); name = Model; sourceTree = ""; @@ -3953,21 +3947,6 @@ name = IntroMessage; sourceTree = ""; }; - 6FE127362C20436A00EB5724 /* HomeRedesign */ = { - isa = PBXGroup; - children = ( - 6FE1273B2C204C0D00EB5724 /* Subviews */, - 6F03CAF82C32C3AA004179A8 /* Messages */, - 6FE127372C20492500EB5724 /* NewTabPage.swift */, - 6FD8E51F2C5BA23200345670 /* NewTabPageViewModel.swift */, - 6FE127392C204BD000EB5724 /* NewTabPageView.swift */, - 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */, - 6FD3F8182C41252900DA5797 /* NewTabPageControllerDelegate.swift */, - 6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */, - ); - name = HomeRedesign; - sourceTree = ""; - }; 6FE1273B2C204C0D00EB5724 /* Subviews */ = { isa = PBXGroup; children = ( @@ -4018,6 +3997,28 @@ name = AdAttribution; sourceTree = ""; }; + 7BF78E002CA2CC100026A1FC /* TipKit */ = { + isa = PBXGroup; + children = ( + 7B1604E72CB685B400A44EC6 /* Logger+TipKit.swift */, + 7BF78E012CA2CC3E0026A1FC /* TipKitAppEventHandling.swift */, + 7B8E0EC52CC81B4800B2B722 /* TipKitController.swift */, + 7B1604EB2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift */, + 7B1604ED2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift */, + ); + path = TipKit; + sourceTree = ""; + }; + 7BFD5FD32C9DA235000FF959 /* TipKit */ = { + isa = PBXGroup; + children = ( + 7BFD5FD42C9DA310000FF959 /* VPNAddWidgetTip.swift */, + 7BFD5FD62C9DB9D7000FF959 /* VPNGeoswitchingTip.swift */, + 7BFD5FD82C9DBC24000FF959 /* VPNSnoozeTip.swift */, + ); + name = TipKit; + sourceTree = ""; + }; 830FA79B1F8E81FB00FCE105 /* ContentBlocker */ = { isa = PBXGroup; children = ( @@ -4178,7 +4179,6 @@ 83ED3B8D1FA8E63700B47556 /* README.md */, 83ED3B8C1FA8E61D00B47556 /* ManualTestsScript.md */, 85A313962028E78A00327D00 /* release_notes.txt */, - 0283A1F82C6E3C3100508FBD /* Recovered References */, ); sourceTree = ""; }; @@ -4227,7 +4227,7 @@ 3157B43627F4C8380042D3D7 /* Favicons */, 839F119520DBC489007CD8C2 /* Feedback */, 85F2FFFE2215C163006BB258 /* FindInPage */, - F13B4BF31F18C73A00814661 /* Home */, + F13B4BF31F18C73A00814661 /* NewTabPage */, 84E341A11E2F7EFB00BDBA6F /* Info.plist */, 98B001B1251EABB40090EC07 /* InfoPlist.strings */, 85DFEDEB24C7CC7600973FE7 /* iPad */, @@ -4246,6 +4246,7 @@ F13B4BF41F18C74500814661 /* Tabs */, F1386BA21E6846320062FC3C /* TabSwitcher */, 98F3A1D6217B36EE0011A0D4 /* Themes */, + 7BF78E002CA2CC100026A1FC /* TipKit */, F11CEF581EBB66C80088E4D7 /* Tutorials */, CB48D32F2B90CE8500631D8B /* UserBehaviorMonitor */, F1D796ED1E7AE4090019D451 /* UserInterface */, @@ -4337,9 +4338,7 @@ 85374D3621AC417200FF5A1E /* Renderers */ = { isa = PBXGroup; children = ( - F446B9B4251150AC00324016 /* HomeMessageViewSectionRenderer.swift */, 85374D3B21AC41E700FF5A1E /* FavoritesHomeViewSectionRenderer.swift */, - 85374D3721AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift */, ); name = Renderers; sourceTree = ""; @@ -4774,6 +4773,8 @@ 9FDEC7B32C8FD62F00C7A692 /* OnboardingAddressBarPositionPickerViewModelTests.swift */, 9FDEC7B92C9006E000C7A692 /* BrowserComparisonModelTests.swift */, 9F1623082C9D14F10093C4FC /* DefaultVariantManagerOnboardingTests.swift */, + 9F8E0F322CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift */, + 9F1798562CD2443F0073018B /* AddToDockPromoViewModelTests.swift */, ); name = Onboarding; sourceTree = ""; @@ -4795,6 +4796,36 @@ path = ContextualOnboarding; sourceTree = ""; }; + 9F8E0F282CCA577E001EA7C5 /* AddToDock */ = { + isa = PBXGroup; + children = ( + 9F8E0F3B2CCFD050001EA7C5 /* Resources */, + 9F8E0F2B2CCA617C001EA7C5 /* VideoPlayer */, + 9F8E0F302CCA6390001EA7C5 /* AddToDockTutorialView.swift */, + 9F8E0F372CCFAA8A001EA7C5 /* AddToDockPromoView.swift */, + 9F8E0F3C2CCFD071001EA7C5 /* AddToDockPromoViewModel.swift */, + ); + path = AddToDock; + sourceTree = ""; + }; + 9F8E0F2B2CCA617C001EA7C5 /* VideoPlayer */ = { + isa = PBXGroup; + children = ( + 9F8E0F2C2CCA618E001EA7C5 /* VideoPlayerView.swift */, + 9F8E0F2E2CCA6202001EA7C5 /* VideoPlayerViewModel.swift */, + ); + path = VideoPlayer; + sourceTree = ""; + }; + 9F8E0F3B2CCFD050001EA7C5 /* Resources */ = { + isa = PBXGroup; + children = ( + 9F72FE262CD223A000BA35F5 /* add-to-dock-promo.json */, + 9F8E0F292CCA5C9D001EA7C5 /* add-to-dock-demo.mp4 */, + ); + path = Resources; + sourceTree = ""; + }; 9F96F73D2C914C3D009E45D5 /* Background */ = { isa = PBXGroup; children = ( @@ -4887,6 +4918,7 @@ 9FF7E9802C22A19800902BE5 /* OnboardingExperiment */ = { isa = PBXGroup; children = ( + 9F8E0F282CCA577E001EA7C5 /* AddToDock */, 9F96F73D2C914C3D009E45D5 /* Background */, 9FDEC7BD2C9125EC00C7A692 /* AddressBarPositionPicker */, 9F9A92322C86B419001D036D /* AppIconPicker */, @@ -5299,7 +5331,6 @@ D6B67A112C332B6E002122EB /* DuckPlayerMocks.swift */, D62EC3BB2C2470E000FC9D04 /* DuckPlayerTests.swift */, D62EC3B82C246A5600FC9D04 /* YoutublePlayerNavigationHandlerTests.swift */, - D6F557B92C8859040034444B /* DuckPlayerExperimentTests.swift */, ); name = DuckPlayer; sourceTree = ""; @@ -5316,7 +5347,6 @@ D63FF8942C1B67E8006DE24D /* YoutubeOverlayUserScript.swift */, D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */, 31860A5A2C57ED2D005561F5 /* DuckPlayerStorage.swift */, - D60E5C2E2C862297007D6BC7 /* DuckPlayerLaunchExperiment.swift */, ); path = DuckPlayer; sourceTree = ""; @@ -5335,6 +5365,7 @@ D664C7962B289AA000CBFA76 /* Extensions */, D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */, BDE219E52C406D19005D5884 /* PrivacyProDataReporting.swift */, + 1E39BEAF2CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift */, ); path = Subscription; sourceTree = ""; @@ -5559,6 +5590,7 @@ EECD94B22A28B8580085C66E /* NetworkProtection */ = { isa = PBXGroup; children = ( + 7BFD5FD32C9DA235000FF959 /* TipKit */, 4BD96E072C4DCCD1003BC32C /* LiveActivity */, 4B37E04E2B928C91009E81CA /* Resources */, EE01EB412AFC1DE10096AAC9 /* PreferredLocation */, @@ -5701,6 +5733,7 @@ 85C11E4A209084DE00BFFEB4 /* HomeRow */, F1BE54481E69DD5F00FCF649 /* Onboarding */, F186421A1E94A3F900B2A911 /* Support */, + 9F8E0F252CC9395D001EA7C5 /* Logger+Onboarding.swift */, ); name = Tutorials; sourceTree = ""; @@ -5765,26 +5798,25 @@ name = TabSwitcher; sourceTree = ""; }; - F13B4BF31F18C73A00814661 /* Home */ = { + F13B4BF31F18C73A00814661 /* NewTabPage */ = { isa = PBXGroup; children = ( - 6FE127362C20436A00EB5724 /* HomeRedesign */, + 6F32EEC32CC2B6B2007F3DA2 /* FavoritesSearchOverlay */, F4D9C4F8251179CC00814B71 /* HomeMessages */, - 984147B324F0264300362052 /* Home.storyboard */, - 853C5F5A21BFF0AE001F7A05 /* HomeCollectionView.swift */, - F1E90C1F1E678E7C005E7E21 /* HomeControllerDelegate.swift */, + 6FE1273B2C204C0D00EB5724 /* Subviews */, + 6F03CAF82C32C3AA004179A8 /* Messages */, 85058365219AE9EA00ED4EDB /* HomePageConfiguration.swift */, 6F03CAFD2C32DD08004179A8 /* HomePageMessagesConfiguration.swift */, - F16390811E648B7A005B4550 /* HomeViewController.swift */, - 564DE4612C4546BE00D23241 /* HomeViewController+DaxDialogs.swift */, 6F40D15A2C34423800BF22F0 /* HomePageDisplayDailyPixelBucket.swift */, - 85058367219C49E000ED4EDB /* HomeViewSectionRenderers.swift */, - 85C861E528FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift */, - 564DE45F2C4544CA00D23241 /* HomePageDependencies.swift */, - 85B9CB8321AEBD72009001F1 /* Cells */, - 85374D3621AC417200FF5A1E /* Renderers */, + 6FE127372C20492500EB5724 /* NewTabPage.swift */, + 6FD8E51F2C5BA23200345670 /* NewTabPageViewModel.swift */, + 6F5041C82CC11A5100989E48 /* SimpleNewTabPageView.swift */, + 6FE127392C204BD000EB5724 /* NewTabPageView.swift */, + 6FE127452C2054A900EB5724 /* NewTabPageViewController.swift */, + 6FD3F8182C41252900DA5797 /* NewTabPageControllerDelegate.swift */, + 6FB1FEA12C256ACD0075B68B /* NewTabPageManager.swift */, ); - name = Home; + name = NewTabPage; sourceTree = ""; }; F13B4BF41F18C74500814661 /* Tabs */ = { @@ -5986,7 +6018,6 @@ children = ( 9F4CC51A2C48C0C7006A96EB /* MockTabDelegate.swift */, C14882E927F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift */, - 98B3128F218CCB2200E54DE1 /* MockDependencyProvider.swift */, C158AC7A297AB5DC0008723A /* MockSecureVault.swift */, F1134EBA1F40D3D000B73467 /* MockStatisticsStore.swift */, 026DABA328242BC80089E0B5 /* MockUserAgent.swift */, @@ -6946,6 +6977,7 @@ AA4D6AA423DE4CC4007E8790 /* AppIconBlue29x29@2x.png in Resources */, AA4D6AE323DE4D33007E8790 /* AppIconGreen60x60@2x.png in Resources */, F41610BC29E5DF66001F709D /* DeprecatedColors.xcassets in Resources */, + 9F8E0F2A2CCA5C9D001EA7C5 /* add-to-dock-demo.mp4 in Resources */, F4F7F10B25813FE200045D62 /* 02_Water_swirl_really_small.json in Resources */, 9880723825FA4E450039EF4B /* menu_light.json in Resources */, 85371D242121B9D500920548 /* new_tab.json in Resources */, @@ -6966,6 +6998,7 @@ 85F98F98296F4CB100742F4A /* SyncAssets.xcassets in Resources */, 31BC5F412C2B0B540004DF37 /* DuckPlayer.xcassets in Resources */, AA4D6A9423DE49A5007E8790 /* AppIconBlack29x29@2x.png in Resources */, + 7BDBAD0E2CBFB3F1000379B7 /* VPN.xcassets in Resources */, 98B001B3251EABB40090EC07 /* InfoPlist.strings in Resources */, AA4D6ACE23DE4D27007E8790 /* AppIconPurple60x60@3x.png in Resources */, D65CEA702B6AC6C9008A759B /* Subscription.xcassets in Resources */, @@ -7020,7 +7053,6 @@ 8517D98B221783A0006A8DD0 /* FindInPage.xcassets in Resources */, 984147C924F02E9E00362052 /* DaxOnboarding.storyboard in Resources */, 858650DB246B111900C36F8A /* DaxOnboarding.xcassets in Resources */, - 984147B124F0264300362052 /* Home.storyboard in Resources */, 984147C324F026C800362052 /* HomeRow.storyboard in Resources */, B6BA95E828924730004ABA20 /* JSAlertController.storyboard in Resources */, AA4D6AF623DF0312007E8790 /* AppIconRed60x60@3x.png in Resources */, @@ -7043,6 +7075,7 @@ AA4D6ABF23DE4D15007E8790 /* AppIconYellow40x40@3x.png in Resources */, AA4D6A8F23DE49A5007E8790 /* AppIconBlack29x29@3x.png in Resources */, AA4D6AA523DE4CC4007E8790 /* AppIconBlue29x29@3x.png in Resources */, + 9F72FE272CD223A000BA35F5 /* add-to-dock-promo.json in Resources */, 1EEF124C2850A93F003DDE57 /* Trackers.xcassets in Resources */, AA4D6ACF23DE4D27007E8790 /* AppIconPurple76x76@2x.png in Resources */, 4B37E0502B928CA6009E81CA /* vpn-light-mode.json in Resources */, @@ -7329,7 +7362,6 @@ 8577A1C5255D2C0D00D43FCD /* HitTestingToolbar.swift in Sources */, 6FE095D82BD90AFB00490FF8 /* UniversalOmniBarState.swift in Sources */, 1DEAADE82BA38AA500E25A97 /* SettingsGeneralView.swift in Sources */, - 853C5F5B21BFF0AE001F7A05 /* HomeCollectionView.swift in Sources */, 6F64AA592C4818D700CF4489 /* NewTabPageShortcut.swift in Sources */, 3132FA2627A0784600DD7A12 /* FilePreviewHelper.swift in Sources */, 9820FF502244FECC008D4782 /* UIScrollViewExtension.swift in Sources */, @@ -7349,9 +7381,12 @@ EE4FB1882A28D11900E5CBA7 /* NetworkProtectionStatusViewModel.swift in Sources */, 6FB1FE9E2C24D41D0075B68B /* NewTabPageSectionsDebugView.swift in Sources */, 8540BD5623D9E9C20057FDD2 /* PreserveLoginsSettingsViewController.swift in Sources */, + 7B8E0EC62CC81B4900B2B722 /* TipKitController.swift in Sources */, + 7B1604EC2CB68BDA00A44EC6 /* TipKitController+ConvenienceInitializers.swift in Sources */, 851672D12BED1FC900592F24 /* AutocompleteView.swift in Sources */, 3161D13227AC161B00285CF6 /* DownloadMetadata.swift in Sources */, D664C7C72B289AA200CBFA76 /* PurchaseInProgressView.swift in Sources */, + 7BFD5FD72C9DB9D7000FF959 /* VPNGeoswitchingTip.swift in Sources */, F1668BCE1E798081008CBA04 /* BookmarksViewController.swift in Sources */, 8590CB69268A4E190089F6BF /* DebugEtagStorage.swift in Sources */, C1CDA3162AFB9C7F006D1476 /* AutofillNeverPromptWebsitesManager.swift in Sources */, @@ -7424,6 +7459,7 @@ 9F5E5AB02C3E4C6000165F54 /* ContextualOnboardingPresenter.swift in Sources */, 310D091B2799F54900DC0060 /* DownloadManager.swift in Sources */, 98D98A7425ED88D100D8E3DF /* BrowsingMenuEntryViewCell.swift in Sources */, + 7BFD5FD92C9DBC24000FF959 /* VPNSnoozeTip.swift in Sources */, 98F3A1D8217B37010011A0D4 /* Theme.swift in Sources */, 4B2C79612C5B27AC00A240CC /* VPNSnoozeActivityAttributes.swift in Sources */, CB9B873C278C8FEA001F4906 /* WidgetEducationView.swift in Sources */, @@ -7465,8 +7501,10 @@ C185ED612BD4329700BAE9DC /* ImportPasswordsStatusHandler.swift in Sources */, CB9B8739278C8E72001F4906 /* WidgetEducationViewController.swift in Sources */, F4D9C4FA25117A0F00814B71 /* HomeMessageStorage.swift in Sources */, + 6F5041C92CC11A5100989E48 /* SimpleNewTabPageView.swift in Sources */, D69FBF762B28BE3600B505F1 /* SettingsSubscriptionView.swift in Sources */, D664C7CC2B289AA200CBFA76 /* SubscriptionPagesUserScript.swift in Sources */, + 9F8E0F262CC9395D001EA7C5 /* Logger+Onboarding.swift in Sources */, AA3D854523D9942200788410 /* AppIconSettingsViewController.swift in Sources */, 85C297042476C1FD0063A335 /* DaxDialogsSettings.swift in Sources */, 8505836F219F424500ED4EDB /* UIViewExtension.swift in Sources */, @@ -7524,6 +7562,7 @@ 3157B43827F4C8490042D3D7 /* FaviconsHelper.swift in Sources */, 85F200042216F5D8006BB258 /* FindInPageView.swift in Sources */, D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */, + 7B1604E82CB685B400A44EC6 /* Logger+TipKit.swift in Sources */, 8548D95E25262B1B005AAE49 /* ViewHighlighter.swift in Sources */, F4D7221026F29A70007D6193 /* BookmarkDetailsCell.swift in Sources */, F1617C131E572E0300DEDCAF /* TabSwitcherViewController.swift in Sources */, @@ -7535,7 +7574,9 @@ C1641EAF2BC2F5140012607A /* ImportPasswordsViewController.swift in Sources */, D63FF8982C1B6A45006DE24D /* DuckPlayer.swift in Sources */, 85B9CB8921AEBDD5009001F1 /* FavoriteHomeCell.swift in Sources */, + 1E39BEB02CC9477200496FBA /* SubscriptionCookieManageEventPixelMapping.swift in Sources */, C1935A102C88D131001AD72D /* AutofillSurveyManager.swift in Sources */, + 7B1604EE2CB68D2600A44EC6 /* TipKitDebugOptionsUIActionHandling.swift in Sources */, 98999D5922FDA41500CBBE1B /* BasicAuthenticationAlert.swift in Sources */, C13B32D22A0E750700A59236 /* AutofillSettingStatus.swift in Sources */, 1DDF40202BA049FA006850D9 /* SettingsRootView.swift in Sources */, @@ -7616,6 +7657,7 @@ BD862E0B2B30F9300073E2EE /* VPNFeedbackFormView.swift in Sources */, 850365F323DE087800D0F787 /* UIImageViewExtension.swift in Sources */, 56D060262C359D2E003BAEB5 /* ContextualOnboardingDialogs.swift in Sources */, + 9F8E0F382CCFAA8A001EA7C5 /* AddToDockPromoView.swift in Sources */, 373608922ABB430D00629E7F /* FavoritesDisplayMode+UserDefaults.swift in Sources */, C160544129D6044D00B715A1 /* AutofillInterfaceUsernameTruncator.swift in Sources */, 31C70B5528045E3500FB6AD1 /* SecureVaultReporter.swift in Sources */, @@ -7627,9 +7669,10 @@ CBFCB30E2B2CD47800253E9E /* ConfigurationURLDebugViewController.swift in Sources */, 6FDC64052C98515E00DB71B3 /* FavoriteAddItemView.swift in Sources */, 982686AD2600C0850011A8D6 /* ActionMessageView.swift in Sources */, - F446B9B5251150AC00324016 /* HomeMessageViewSectionRenderer.swift in Sources */, D6E0C1852B7A2B9400D5E1E9 /* DesktopDownloadPlatformConstants.swift in Sources */, + 9F8E0F312CCA6390001EA7C5 /* AddToDockTutorialView.swift in Sources */, D6BFCB5F2B7524AA0051FF81 /* SubscriptionPIRView.swift in Sources */, + 9F8E0F2F2CCA6202001EA7C5 /* VideoPlayerViewModel.swift in Sources */, 98D98A8225ED88E300D8E3DF /* BrowsingMenuSeparatorViewCell.swift in Sources */, D63657192A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift in Sources */, 1E4FAA6427D8DFB900ADC5B3 /* OngoingDownloadRowViewModel.swift in Sources */, @@ -7660,11 +7703,9 @@ F194FAED1F14E2B3009B4DF8 /* UIFontExtension.swift in Sources */, 98F0FC2021FF18E700CE77AB /* AutoClearSettingsViewController.swift in Sources */, D67969112BC84CE700BA8B34 /* SubscriptionContainerViewModel.swift in Sources */, - F1E90C201E678E7C005E7E21 /* HomeControllerDelegate.swift in Sources */, 85DB12ED2A1FED0C000A4A72 /* AppDelegate+AppDeepLinks.swift in Sources */, 98DA6ECA2181E41F00E65433 /* ThemeManager.swift in Sources */, F1D43AFC2B99C56000BAB743 /* RootDebugViewController+VanillaBrowser.swift in Sources */, - 564DE4602C4544CA00D23241 /* HomePageDependencies.swift in Sources */, C159DF072A430B60007834BB /* EmailSignupViewController.swift in Sources */, 37A6A8FE2AFD0208008580A3 /* FaviconsFetcherOnboarding.swift in Sources */, F1CA3C3B1F045B65005FADB3 /* Authenticator.swift in Sources */, @@ -7679,6 +7720,7 @@ 31CC224928369B38001654A4 /* AutofillLoginSettingsListViewController.swift in Sources */, F1D796EC1E7AB8930019D451 /* SaveBookmarkActivity.swift in Sources */, F4B0B78C252CAFF700830156 /* OnboardingWidgetsViewController.swift in Sources */, + 7BFD5FD52C9DA310000FF959 /* VPNAddWidgetTip.swift in Sources */, C17B595A2A03AAD30055F2D1 /* PasswordGenerationPromptViewController.swift in Sources */, 8531A08E1F9950E6000484F0 /* UnprotectedSitesViewController.swift in Sources */, CBD4F13C279EBF4A00B20FD7 /* HomeMessage.swift in Sources */, @@ -7686,7 +7728,6 @@ 1DEAADEC2BA45B4500E25A97 /* SettingsAccessibilityView.swift in Sources */, 85C8E61D2B0E47380029A6BD /* BookmarksDatabaseSetup.swift in Sources */, 3132FA2C27A07A1B00DD7A12 /* FilePreview.swift in Sources */, - 85C861E628FF1B5F00189466 /* HomeViewSectionRenderersExtension.swift in Sources */, CB825C922C071B1400BCC586 /* AlertView.swift in Sources */, 1DDF40292BA04FCD006850D9 /* SettingsPrivacyProtectionsView.swift in Sources */, BDE91CD82C629A910005CB74 /* UnifiedFeedbackSender.swift in Sources */, @@ -7742,7 +7783,6 @@ 85058369219F424500ED4EDB /* UIColorExtension.swift in Sources */, BDE219E62C406D19005D5884 /* PrivacyProDataReporting.swift in Sources */, D6E83C312B1EA309006C8AFB /* SettingsCell.swift in Sources */, - 85058368219C49E000ED4EDB /* HomeViewSectionRenderers.swift in Sources */, 1DEAADEE2BA45DFE00E25A97 /* SettingsDataClearingView.swift in Sources */, 6F96FF102C2B128500162692 /* NewTabPageCustomizeButtonView.swift in Sources */, EE01EB432AFC1E0A0096AAC9 /* NetworkProtectionVPNLocationView.swift in Sources */, @@ -7788,7 +7828,6 @@ F4F6DFB826EA9AA600ED7E12 /* BookmarksTextFieldCell.swift in Sources */, 6FE127402C204D9B00EB5724 /* ShortcutsView.swift in Sources */, 85F98F92296F32BD00742F4A /* SyncSettingsViewController.swift in Sources */, - D60E5C2F2C862297007D6BC7 /* DuckPlayerLaunchExperiment.swift in Sources */, 84E341961E2F7EFB00BDBA6F /* AppDelegate.swift in Sources */, 310D091D2799F57200DC0060 /* Download.swift in Sources */, BDE91CDA2C62A70B0005CB74 /* UnifiedMetadataCollector.swift in Sources */, @@ -7818,6 +7857,7 @@ 6FD3F80F2C3EF4F000DA5797 /* DeviceOrientationEnvironmentValue.swift in Sources */, 85864FBC24D31EF300E756FF /* SuggestionTrayViewController.swift in Sources */, D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */, + 9F8E0F3D2CCFD072001EA7C5 /* AddToDockPromoViewModel.swift in Sources */, 1EF24235273BB9D200DE3D02 /* IntervalSlider.swift in Sources */, F1D796EE1E7AF2EB0019D451 /* UIViewControllerExtension.swift in Sources */, 1EE411F12857C3640003FE64 /* TrackerAnimationImageProvider.swift in Sources */, @@ -7826,6 +7866,7 @@ F15D43201E706CC500BF2CDC /* AutocompleteViewController.swift in Sources */, BD862E092B30F63E0073E2EE /* VPNMetadataCollector.swift in Sources */, D6E83C682B23B6A3006C8AFB /* FontSettings.swift in Sources */, + 7BF78E022CA2CC3E0026A1FC /* TipKitAppEventHandling.swift in Sources */, 1DEAADF62BA4809400E25A97 /* CookiePopUpProtectionView.swift in Sources */, 31EF52E1281B3BDC0034796E /* AutofillLoginListItemViewModel.swift in Sources */, C1EA86602C74CB6C00E8604D /* SyncPromoView.swift in Sources */, @@ -7847,7 +7888,6 @@ 1DEAADEA2BA4539800E25A97 /* SettingsAppearanceView.swift in Sources */, B623C1C22862CA9E0043013E /* DownloadSession.swift in Sources */, 9F7CFF7F2C8A94F70012833E /* OnboardingView+AddressBarPositionContent.swift in Sources */, - F16390821E648B7A005B4550 /* HomeViewController.swift in Sources */, 985892522260B1B200EEB31B /* ProgressView.swift in Sources */, 85BA585A1F3506AE00C6E8CA /* AppSettings.swift in Sources */, C1FFBD482C7749A90073622B /* SyncSettingsViewController+PlatformLinks.swift in Sources */, @@ -7862,10 +7902,11 @@ 1E4DCF4627B6A33600961E25 /* DownloadsListViewModel.swift in Sources */, 37C696772C4957940073E131 /* RemoteMessagingDebugViewController.swift in Sources */, 31860A5B2C57ED2D005561F5 /* DuckPlayerStorage.swift in Sources */, - 6FEC0B882C999961006B4F6E /* FavoriteDataSource.swift in Sources */, + 6FEC0B882C999961006B4F6E /* FavoritesListInteractingAdapter.swift in Sources */, F4F6DFB626E6B71300ED7E12 /* BookmarkFoldersTableViewController.swift in Sources */, 8586A11024CCCD040049720E /* TabsBarViewController.swift in Sources */, 6FEC0B852C999352006B4F6E /* FavoriteItem.swift in Sources */, + 9F8E0F2D2CCA618E001EA7C5 /* VideoPlayerView.swift in Sources */, F1D796F41E7C2A410019D451 /* BookmarksDelegate.swift in Sources */, D664C7B92B289AA200CBFA76 /* WKUserContentController+Handler.swift in Sources */, 1E8AD1D727C2E24E00ABA377 /* DownloadsListRowViewModel.swift in Sources */, @@ -7880,7 +7921,6 @@ 6FE127462C2054A900EB5724 /* NewTabPageViewController.swift in Sources */, 984D60B2222A1284003B9E3B /* FeedbackFormViewController.swift in Sources */, 31A42564285A09E800049386 /* FaviconView.swift in Sources */, - 85374D3821AC419800FF5A1E /* NavigationSearchHomeViewSectionRenderer.swift in Sources */, 6FA3438F2C3D3BC300470677 /* Favorite.swift in Sources */, 98E888F2223FCC4A00B608A4 /* OnboardingViewController.swift in Sources */, C1B7B51C28941E980098FD6A /* HomeMessageViewModelBuilder.swift in Sources */, @@ -7895,7 +7935,6 @@ 1E4DCF4A27B6A38000961E25 /* DownloadListRepresentable.swift in Sources */, 1DEAADFB2BA71E9A00E25A97 /* SettingsPrivacyProtectionDescriptionView.swift in Sources */, 6F3537A02C4AAFD2009F8717 /* NewTabPageSettingsSectionItemView.swift in Sources */, - 564DE4622C4546BE00D23241 /* HomeViewController+DaxDialogs.swift in Sources */, 2DC3FC65C6D9DA634426672D /* AutofillNoAuthAvailableView.swift in Sources */, 6F03CAFC2C32C6F6004179A8 /* NewTabPageMessagesModel.swift in Sources */, ); @@ -7918,7 +7957,7 @@ 98DA35C4268CC81E00159906 /* DomainMatchingReportTests.swift in Sources */, 8590CB632684F10F0089F6BF /* ContentBlockerProtectionStoreTests.swift in Sources */, 83EDCC411F86B89C005CDFCD /* StatisticsLoaderTests.swift in Sources */, - 564DE4572C4150E600D23241 /* HomeViewControllerDaxDialogTests.swift in Sources */, + 564DE4572C4150E600D23241 /* NewTabPageControllerDaxDialogTests.swift in Sources */, C14882E327F20D9A00D59F0C /* BookmarksExporterTests.swift in Sources */, 85C29708247BDD060063A335 /* DaxDialogsBrowsingSpecTests.swift in Sources */, 9FE05CF12C36468A00D9046B /* OnboardingPixelReporterTests.swift in Sources */, @@ -7934,9 +7973,7 @@ F13B4BF91F18CA0600814661 /* TabsModelTests.swift in Sources */, F1BDDBFD2C340D9C00459306 /* SubscriptionContainerViewModelTests.swift in Sources */, 987243142C5232B5007ECC76 /* BookmarksDatabaseSetupTests.swift in Sources */, - 98B31290218CCB2200E54DE1 /* MockDependencyProvider.swift in Sources */, CBDD5DDF29A6736A00832877 /* APIHeadersTests.swift in Sources */, - D6F557BA2C8859040034444B /* DuckPlayerExperimentTests.swift in Sources */, 986B45D0299E30A50089D2D7 /* BookmarkEntityTests.swift in Sources */, 981C49B02C8FA61D00DF11E8 /* DataStoreIdManagerTests.swift in Sources */, B6AD9E3828D4512E0019CDE9 /* EmbeddedTrackerDataTests.swift in Sources */, @@ -7995,12 +8032,14 @@ 987130C5294AAB9F00AB05E0 /* BookmarkEditorViewModelTests.swift in Sources */, BDFF03262BA3DA4900F324C9 /* NetworkProtectionFeatureVisibilityTests.swift in Sources */, 0283A2042C6E572F00508FBD /* BrokenSitePromptLimiterTests.swift in Sources */, + 9F8E0F332CCA642D001EA7C5 /* VideoPlayerViewModelTests.swift in Sources */, D62EC3BA2C246A7000FC9D04 /* YoutublePlayerNavigationHandlerTests.swift in Sources */, 1EAABE712C99FC75003F5137 /* SubscriptionFeatureAvailabilityMock.swift in Sources */, 8341D807212D5E8D000514C2 /* HashExtensionTest.swift in Sources */, C1D21E2F293A599C006E5A05 /* AutofillLoginSessionTests.swift in Sources */, 85D2187924BF6B8B004373D2 /* FaviconSourcesProviderTests.swift in Sources */, 9F69331B2C5A16E200CD6A5D /* OnboardingDaxFavouritesTests.swift in Sources */, + 9F1798572CD2443F0073018B /* AddToDockPromoViewModelTests.swift in Sources */, 6F7FB8E52C66158D00867DA7 /* NewTabPageShortcutsSettingsModelTests.swift in Sources */, 983BD6B52B34760600AAC78E /* MockPrivacyConfiguration.swift in Sources */, 1E8146AD28C8ABF000D1AF63 /* TrackerAnimationLogicTests.swift in Sources */, @@ -8030,6 +8069,7 @@ C14882EA27F20DD000D59F0C /* MockBookmarksCoreDataStorage.swift in Sources */, 1E05D1DB29C47B3300BF9A1F /* DailyPixelTests.swift in Sources */, 564DE4552C3EDEF200D23241 /* ContextualOnboardingNewTabDialogFactoryTests.swift in Sources */, + 6F5AA3EF2CC1588400685CB4 /* FavoritesListInteractingAdapterTests.swift in Sources */, 981FED7422046017008488D7 /* AutoClearTests.swift in Sources */, 98DDF9F322C4029D00DE38DB /* InitHelpers.swift in Sources */, B6AD9E3628D4510A0019CDE9 /* ContentBlockerRulesManagerMock.swift in Sources */, @@ -8581,38 +8621,6 @@ name = Feedback.storyboard; sourceTree = ""; }; - 984147B324F0264300362052 /* Home.storyboard */ = { - isa = PBXVariantGroup; - children = ( - 984147B224F0264300362052 /* Base */, - 9866DB8E251CA8F400612E3A /* bg */, - 9866DBA5251CA91800612E3A /* hr */, - 9866DBBC251CA92A00612E3A /* cs */, - 9866DBD3251CA93900612E3A /* da */, - 9866DBEA251CA94F00612E3A /* nl */, - 9866DC01251CA96200612E3A /* et */, - 9866DC18251CA99B00612E3A /* de */, - 9866DC2F251CA9B000612E3A /* el */, - 9866DC46251CA9C000612E3A /* hu */, - 9866DC5D251CA9CE00612E3A /* it */, - 9866DC74251CA9E300612E3A /* lv */, - 9866DC8B251CA9F500612E3A /* lt */, - 9866DCA2251CAA0500612E3A /* pl */, - 9866DCB9251CAA2600612E3A /* ro */, - 9866DCD0251CAA3400612E3A /* sk */, - 9866DCE7251CAA4800612E3A /* sl */, - 9866DD5C251CB10000612E3A /* fi */, - 9866DD5E251CB10100612E3A /* fr */, - 9866DD62251CB10400612E3A /* pt */, - 9866DD64251CB10500612E3A /* ru */, - 9866DD66251CB10600612E3A /* es */, - 9866DD68251CB10700612E3A /* sv */, - 9866DD6A251CB10800612E3A /* tr */, - 981685462521EEF000FA91A1 /* nb */, - ); - name = Home.storyboard; - sourceTree = ""; - }; 984147B624F0264B00362052 /* Authentication.storyboard */ = { isa = PBXVariantGroup; children = ( @@ -10962,7 +10970,7 @@ repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 200.0.0; + version = 202.3.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 3a477518cb..2cbd2c54ae 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" : "9f62aacd878a0c05bff7256eb25b8776aa4e917f", - "version" : "200.0.0" + "revision" : "7b78b46340c9981b9774352be08c3c28b3a19011", + "version" : "202.3.0" } }, { @@ -41,8 +41,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/content-scope-scripts", "state" : { - "revision" : "1ed569676555d493c9c5575eaed22aa02569aac9", - "version" : "6.19.0" + "revision" : "48fee2508995d4ac02d18b3d55424adedcb4ce4f", + "version" : "6.28.0" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "945ac09a0189dc6736db617867fde193ea984b20", - "version" : "15.0.0" + "revision" : "c992041d16ec10d790e6204dce9abf9966d1363c", + "version" : "15.1.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 0442bb02d9..b5448d7b67 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -88,8 +88,14 @@ import os.log private var autofillUsageMonitor = AutofillUsageMonitor() private(set) var subscriptionFeatureAvailability: SubscriptionFeatureAvailability! + private var subscriptionCookieManager: SubscriptionCookieManaging! + private var subscriptionCookieManagerFeatureFlagCancellable: AnyCancellable? var privacyProDataReporter: PrivacyProDataReporting! + // MARK: - Feature specific app event handlers + + private let tipKitAppEventsHandler = TipKitAppEventHandler() + // MARK: lifecycle @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) @@ -309,6 +315,8 @@ import os.log privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, purchasePlatform: .appStore) + subscriptionCookieManager = makeSubscriptionCookieManager() + homePageConfiguration = HomePageConfiguration(variantManager: AppDependencyProvider.shared.variantManager, remoteMessagingClient: remoteMessagingClient, privacyProDataReporter: privacyProDataReporter) @@ -346,7 +354,8 @@ import os.log contextualOnboardingLogic: daxDialogs, contextualOnboardingPixelReporter: onboardingPixelReporter, subscriptionFeatureAvailability: subscriptionFeatureAvailability, - voiceSearchHelper: voiceSearchHelper) + voiceSearchHelper: voiceSearchHelper, + subscriptionCookieManager: subscriptionCookieManager) main.loadViewIfNeeded() syncErrorHandler.alertPresenter = main @@ -394,9 +403,51 @@ import os.log didCrashDuringCrashHandlersSetUp = false } + tipKitAppEventsHandler.appDidFinishLaunching() + return true } + private func makeSubscriptionCookieManager() -> SubscriptionCookieManaging { + let subscriptionCookieManager = SubscriptionCookieManager(subscriptionManager: AppDependencyProvider.shared.subscriptionManager, + currentCookieStore: { [weak self] in + guard self?.mainViewController?.tabManager.model.hasActiveTabs ?? false else { + // We shouldn't interact with WebKit's cookie store unless we have a WebView, + // eventually the subscription cookie will be refreshed on opening the first tab + return nil + } + + return WKWebsiteDataStore.current().httpCookieStore + }, eventMapping: SubscriptionCookieManageEventPixelMapping()) + + + let privacyConfigurationManager = ContentBlocking.shared.privacyConfigurationManager + + // Enable subscriptionCookieManager if feature flag is present + if privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.setAccessTokenCookieForSubscriptionDomains) { + subscriptionCookieManager.enableSettingSubscriptionCookie() + } + + // Keep track of feature flag changes + subscriptionCookieManagerFeatureFlagCancellable = privacyConfigurationManager.updatesPublisher + .sink { [weak self, weak privacyConfigurationManager] in + guard let self, let privacyConfigurationManager else { return } + + let isEnabled = privacyConfigurationManager.privacyConfig.isSubfeatureEnabled(PrivacyProSubfeature.setAccessTokenCookieForSubscriptionDomains) + + Task { [weak self] in + if isEnabled { + self?.subscriptionCookieManager.enableSettingSubscriptionCookie() + await self?.subscriptionCookieManager.refreshSubscriptionCookie() + } else { + await self?.subscriptionCookieManager.disableSettingSubscriptionCookie() + } + } + } + + return subscriptionCookieManager + } + private func makeHistoryManager() -> HistoryManaging { let provider = AppDependencyProvider.shared @@ -568,6 +619,10 @@ import os.log } } + Task { @MainActor in + await subscriptionCookieManager.refreshSubscriptionCookie() + } + let importPasswordsStatusHandler = ImportPasswordsStatusHandler(syncService: syncService) importPasswordsStatusHandler.checkSyncSuccessStatus() diff --git a/DuckDuckGo/AppDependencyProvider.swift b/DuckDuckGo/AppDependencyProvider.swift index 7460cb6bf4..4997f73e56 100644 --- a/DuckDuckGo/AppDependencyProvider.swift +++ b/DuckDuckGo/AppDependencyProvider.swift @@ -139,9 +139,4 @@ final class AppDependencyProvider: DependencyProvider { accountManager: accountManager) } - /// Only meant to be used for testing. - /// - static func makeTestingInstance() -> Self { - Self.init() - } } diff --git a/DuckDuckGo/AppSettings.swift b/DuckDuckGo/AppSettings.swift index e2c31dda2b..2e212f2e59 100644 --- a/DuckDuckGo/AppSettings.swift +++ b/DuckDuckGo/AppSettings.swift @@ -87,4 +87,5 @@ protocol AppSettings: AnyObject, AppDebugSettings { protocol AppDebugSettings { var onboardingHighlightsEnabled: Bool { get set } + var onboardingAddToDockEnabled: Bool { get set } } diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index 408b03a116..9d1ca894a3 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -418,10 +418,12 @@ public class AppUserDefaults: AppSettings { @UserDefaultsWrapper(key: .duckPlayerOpenInNewTab, defaultValue: true) var duckPlayerOpenInNewTab: Bool - @UserDefaultsWrapper(key: .debugOnboardingHighlightsEnabledKey, defaultValue: false) var onboardingHighlightsEnabled: Bool + + @UserDefaultsWrapper(key: .debugOnboardingAddToDockEnabledKey, defaultValue: false) + var onboardingAddToDockEnabled: Bool } extension AppUserDefaults: AppConfigurationFetchStatistics { diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index 0590d9d105..1b1f441b79 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,i=!1){let c=null;return c=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&&(c=c.filter((e=>{const o=e.textContent.toLowerCase();if(Array.isArray(t.textFilter)){let e=!1;for(const i of t.textFilter)if(-1!==o.indexOf(i.toLowerCase())){e=!0;break}return e}if(null!=t.textFilter)return-1!==o.indexOf(t.textFilter.toLowerCase())}))),null!=t.styleFilters&&(c=c.filter((e=>{const o=window.getComputedStyle(e);let i=!0;for(const e of t.styleFilters){const t=o[e.option];i=e.negated?i&&t!==e.value:i&&t===e.value}return i}))),null!=t.displayFilter&&(c=c.filter((e=>t.displayFilter?0!==e.offsetHeight:0===e.offsetHeight))),null!=t.iframeFilter&&(c=c.filter((()=>t.iframeFilter?window.location!==window.parent.location:window.location===window.parent.location))),null!=t.childFilter&&(c=c.filter((o=>{const i=e.base;e.setBase(o);const c=e.find(t.childFilter);return e.setBase(i),null!=c.target}))),i?c:(c.length>1&&console.warn("Multiple possible targets: ",c,t,o),c[0])}static find(t,o=!1){const i=[];if(null!=t.parent){const c=e.findElement(t.parent,null,o);if(null!=c){if(c instanceof Array)return c.forEach((c=>{const n=e.findElement(t.target,c,o);n instanceof Array?n.forEach((e=>{i.push({parent:c,target:e})})):i.push({parent:c,target:n})})),i;{const n=e.findElement(t.target,c,o);n instanceof Array?n.forEach((e=>{i.push({parent:c,target:e})})):i.push({parent:c,target:n})}}}else{const c=e.findElement(t.target,null,o);c instanceof Array?c.forEach((e=>{i.push({parent:null,target:e})})):i.push({parent:null,target:c})}return 0===i.length&&i.push({parent:null,target:null}),o?i:(1!==i.length&&console.warn("Multiple results found, even though multiple false",i),i[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 i(e,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(c)}(e);case"list":return async function(e,t){for(const o of e.actions)await i(o,t)}(e,a);case"consent":return async function(e,t){for(const c of e.consents){const e=-1!==t.indexOf(c.type);if(c.matcher&&c.toggleAction){o(c.matcher)!==e&&await i(c.toggleAction)}else e?await i(c.trueAction):await i(c.falseAction)}}(e,a);case"ifcss":return async function(e,o){const c=t.find(e);c.target?e.falseAction&&await i(e.falseAction,o):e.trueAction&&await i(e.trueAction,o)}(e,a);case"waitcss":return async function(e){await new Promise((o=>{let i=e.retries||10;const c=e.waitTime||250,n=()=>{const a=t.find(e);(e.negated&&a.target||!e.negated&&!a.target)&&i>0?(i-=1,setTimeout(n,c)):o()};n()}))}(e);case"foreach":return async function(e,o){const c=t.find(e,!0),n=t.base;for(const n of c)n.target&&(t.setBase(n.target),await i(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),i=t.find(e.dragTarget);if(o.target){const e=o.target.getBoundingClientRect(),t=i.target.getBoundingClientRect();let c=t.top-e.top,n=t.left-e.left;"y"===this.config.axis.toLowerCase()&&(n=0),"x"===this.config.axis.toLowerCase()&&(c=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+c,r+n,l+c,!1,!1,!1,!1,0,o.target);const u=document.createEvent("MouseEvents");u.initMouseEvent("mouseup",!0,!0,window,0,a+n,s+c,r+n,l+c,!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 c=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_KLARO_OPEN_POPUP:()=>{klaro.show(void 0,!0)},EVAL_KLARO_TRY_API_OPT_OUT:()=>{if(window.klaro&&"function"==typeof klaro.show&&"function"==typeof klaro.getManager)try{return klaro.getManager().changeAll(!1),klaro.getManager().saveAndApplyConsents(),!0}catch(e){return console.warn(e),!1}return!1},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_TRUSTARC_FRAME_TEST:()=>window&&window.QueryString&&"0"===window.QueryString.preferences,EVAL_TRUSTARC_FRAME_GTM:()=>window&&window.QueryString&&"1"===window.QueryString.gtm,EVAL_ABC_TEST:()=>document.cookie.includes("trackingconsent"),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_FIDES_DETECT_POPUP:()=>window.Fides?.initialized,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_KETCH_TEST:()=>document.cookie.includes("_ketch_consent_v1_"),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_OPENAI_TEST:()=>document.cookie.includes("oai-allow-ne=false"),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_ROBLOX_TEST:()=>document.cookie.includes("RBXcb"),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_TESLA_TEST:()=>document.cookie.includes("tsla-cookie-consent=rejected"),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_TWCC_TEST:()=>document.cookie.includes("twCookieConsent="),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 i=!1;try{i=!!t.call(globalThis)}catch(t){o.evals&&console.error("error evaluating rule",e,t)}return Promise.resolve(i)}const i=`(${t.toString()})()`;return o.evals&&console.log("async eval:",e,i),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const i=new s(o);return r.pending.set(i.id,i),i.promise}(i,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 i=await this.evaluateRuleStep(e.if);o.rulesteps&&console.log("Condition is",i),i?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)||i(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 i=`${t} { ${"opacity"===o?"opacity: 0":"display: none"} !important; z-index: -1 !important; pointer-events: none !important; } `;return e instanceof HTMLStyleElement&&(e.innerText+=i,t.length>0)}async function b(e,t,o){const i=await e();return!i&&t>0?new Promise((i=>{setTimeout((async()=>{i(b(e,t-1,o))}),o)})):Promise.resolve(i)}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=(i=t,globalThis.structuredClone?structuredClone(i):JSON.parse(JSON.stringify(i)));var i;for(const i of Object.keys(t))void 0!==e[i]&&(o[i]=e[i]);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!0}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.wait(500),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!0}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(){if(await this.mainWorldEval("EVAL_TRUSTARC_FRAME_TEST"))return!0;let e=3e3;return await this.mainWorldEval("EVAL_TRUSTARC_FRAME_GTM")&&(e=1500),await b((()=>"complete"===document.readyState),20,100),await this.waitForElement(".mainContent[aria-hidden=false]",e),!!this.click(".rejectAll")||(this.elementExists(".prefPanel")&&await this.waitForElement('.prefPanel[style="visibility: visible;"]',e),this.click("#catDetails0")?(this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",e),!0):this.click(".required")?(this.waitForThenClick("#gwt-debug-close_id",e),!0):(await this.navigateToSettings(),this.click(".switch span:nth-child(1):not(.active)",!0),this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",10*e),!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}async test(){return await this.wait(500),await this.mainWorldEval("EVAL_TRUSTARC_FRAME_TEST")}},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,#_evidon-background"),await this.waitForThenClick("#_evidon-option-button"),await this.waitForElement("#evidon-prefdiag-overlay",5e3),await this.wait(500),await this.waitForThenClick("#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!!await this.mainWorldEval("EVAL_KLARO_TRY_API_OPT_OUT")||(!!this.click(".klaro .cn-decline")||(await this.mainWorldEval("EVAL_KLARO_OPEN_POPUP"),!!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)}},class extends d{constructor(){super(...arguments),this.name="Admiral"}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("div > div[class*=Card] > div[class*=Frame] > div[class*=Pills] > button[class*=Pills__StyledPill]")}async detectPopup(){return this.elementVisible("div > div[class*=Card] > div[class*=Frame] > div[class*=Pills] > button[class*=Pills__StyledPill]","any")}async optOut(){const e="xpath///button[contains(., 'Afvis alle') or contains(., 'Reject all') or contains(., 'Odbaci sve') or contains(., 'Rechazar todo') or contains(., 'Atmesti visus') or contains(., 'Odmítnout vše') or contains(., 'Απόρριψη όλων') or contains(., 'Rejeitar tudo') or contains(., 'Tümünü reddet') or contains(., 'Отклонить все') or contains(., 'Noraidīt visu') or contains(., 'Avvisa alla') or contains(., 'Odrzuć wszystkie') or contains(., 'Alles afwijzen') or contains(., 'Отхвърляне на всички') or contains(., 'Rifiuta tutto') or contains(., 'Zavrni vse') or contains(., 'Az összes elutasítása') or contains(., 'Respingeți tot') or contains(., 'Alles ablehnen') or contains(., 'Tout rejeter') or contains(., 'Odmietnuť všetko') or contains(., 'Lükka kõik tagasi') or contains(., 'Hylkää kaikki')]";if(await this.waitForElement(e,500))return this.click(e);const t="xpath///button[contains(., 'Spara & avsluta') or contains(., 'Save & exit') or contains(., 'Uložit a ukončit') or contains(., 'Enregistrer et quitter') or contains(., 'Speichern & Verlassen') or contains(., 'Tallenna ja poistu') or contains(., 'Išsaugoti ir išeiti') or contains(., 'Opslaan & afsluiten') or contains(., 'Guardar y salir') or contains(., 'Shrani in zapri') or contains(., 'Uložiť a ukončiť') or contains(., 'Kaydet ve çıkış yap') or contains(., 'Сохранить и выйти') or contains(., 'Salvesta ja välju') or contains(., 'Salva ed esci') or contains(., 'Gem & afslut') or contains(., 'Αποθήκευση και έξοδος') or contains(., 'Saglabāt un iziet') or contains(., 'Mentés és kilépés') or contains(., 'Guardar e sair') or contains(., 'Zapisz & zakończ') or contains(., 'Salvare și ieșire') or contains(., 'Spremi i izađi') or contains(., 'Запазване и изход')]";if(await this.waitForThenClick("xpath///button[contains(., 'Zwecke') or contains(., 'Σκοποί') or contains(., 'Purposes') or contains(., 'Цели') or contains(., 'Eesmärgid') or contains(., 'Tikslai') or contains(., 'Svrhe') or contains(., 'Cele') or contains(., 'Účely') or contains(., 'Finalidades') or contains(., 'Mērķi') or contains(., 'Scopuri') or contains(., 'Fines') or contains(., 'Ändamål') or contains(., 'Finalités') or contains(., 'Doeleinden') or contains(., 'Tarkoitukset') or contains(., 'Scopi') or contains(., 'Amaçlar') or contains(., 'Nameni') or contains(., 'Célok') or contains(., 'Formål')]")&&await this.waitForVisible(t)){return this.elementSelector(t)[0].parentElement.parentElement.querySelectorAll("input[type=checkbox]:checked").forEach((e=>e.click())),this.click(t)}return!1}async optIn(){return this.click("xpath///button[contains(., 'Sprejmi vse') or contains(., 'Prihvati sve') or contains(., 'Godkänn alla') or contains(., 'Prijať všetko') or contains(., 'Принять все') or contains(., 'Aceptar todo') or contains(., 'Αποδοχή όλων') or contains(., 'Zaakceptuj wszystkie') or contains(., 'Accetta tutto') or contains(., 'Priimti visus') or contains(., 'Pieņemt visu') or contains(., 'Tümünü kabul et') or contains(., 'Az összes elfogadása') or contains(., 'Accept all') or contains(., 'Приемане на всички') or contains(., 'Accepter alle') or contains(., 'Hyväksy kaikki') or contains(., 'Tout accepter') or contains(., 'Alles accepteren') or contains(., 'Aktsepteeri kõik') or contains(., 'Přijmout vše') or contains(., 'Alles akzeptieren') or contains(., 'Aceitar tudo') or contains(., 'Acceptați tot')]")}}],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),i=new Array(o.length);return o.forEach(((e,t)=>{i[t]=_(e)})),"none"===t?i.every((e=>!e)):0!==i.length&&("any"===t?i.some((e=>e)):i.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),i=document.evaluate(o,t,null,XPathResult.ANY_TYPE,null);let c=null;const n=[];for(;c=i.iterateNext();)n.push(c);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 i of e){if(t=this.querySingleReplySelector(i,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:"aa",vendorUrl:"https://aa.com",prehideSelectors:[],cosmetic:!0,detectCmp:[{exists:"#aa_optoutmulti-Modal,#cookieBannerMessage"}],detectPopup:[{visible:"#aa_optoutmulti-Modal,#cookieBannerMessage"}],optIn:[{hide:"#aa_optoutmulti-Modal,#cookieBannerMessage"},{waitForThenClick:"#aa_optoutmulti_checkBox"},{waitForThenClick:"#aa_optoutmulti-Modal button.optoutmulti_button"}],optOut:[{hide:"#aa_optoutmulti-Modal,#cookieBannerMessage"}]},{name:"abc",vendorUrl:"https://abc.net.au",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?abc\\.net\\.au/"},prehideSelectors:[],detectCmp:[{exists:"[data-component=CookieBanner]"}],detectPopup:[{visible:"[data-component=CookieBanner] [data-component=CookieBanner_AcceptAll]"}],optIn:[{waitForThenClick:"[data-component=CookieBanner] [data-component=CookieBanner_AcceptAll]"}],optOut:[{waitForThenClick:"[data-component=CookieBanner] [data-component=CookieBanner_AcceptABCRequired]"}],test:[{eval:"EVAL_ABC_TEST"}]},{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:"acris",prehideSelectors:["div.acris-cookie-consent"],detectCmp:[{exists:"[data-acris-cookie-consent]"}],detectPopup:[{visible:".acris-cookie-consent.is--modal"}],optIn:[{waitForVisible:"#ccConsentAcceptAllButton",check:"any"},{wait:500},{waitForThenClick:"#ccConsentAcceptAllButton"}],optOut:[{waitForVisible:"#ccAcceptOnlyFunctional",check:"any"},{wait:500},{waitForThenClick:"#ccAcceptOnlyFunctional"}]},{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,#voyager-gdpr > div"}],detectPopup:[{visible:"#gdpr-new-container,#voyager-gdpr > div"}],optIn:[{waitForThenClick:"#gdpr-new-container .btn-accept,#voyager-gdpr > div > div > button:nth-child(1)"}],optOut:[{if:{exists:"#voyager-gdpr > div"},then:[{waitForThenClick:"#voyager-gdpr > div > div > button:nth-child(2)"}],else:[{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",prehideSelectors:["#consent-tracking"],detectCmp:[{exists:"#consent-tracking"}],detectPopup:[{exists:"#consent-tracking"}],optIn:[{waitForThenClick:"#consent-tracking .affirm.btn"}],optOut:[{if:{exists:"#consent-tracking .decline.btn"},then:[{click:"#consent-tracking .decline.btn"}],else:[{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:"check24-partnerprogramm-de",prehideSelectors:["[data-modal-content]:has([data-toggle-target^='cookie'])"],detectCmp:[{exists:"[data-toggle-target^='cookie']"}],detectPopup:[{visible:"[data-toggle-target^='cookie']",check:"any"}],optIn:[{waitForThenClick:"[data-cookie-accept-all]"}],optOut:[{waitForThenClick:"[data-cookie-dismiss-all]"}]},{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:[{if:{exists:".cmp-pref-link"},then:[{click:".cmp-pref-link"},{waitForThenClick:".cmp-body [id*=rejectAll]"},{waitForThenClick:".cmp-body .cmp-save-btn"}]}]}]},{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:"cookiecuttr",vendorUrl:"https://github.com/cdwharton/cookieCuttr",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:""},prehideSelectors:[".cc-cookies"],detectCmp:[{exists:".cc-cookies .cc-cookie-accept"}],detectPopup:[{visible:".cc-cookies .cc-cookie-accept"}],optIn:[{waitForThenClick:".cc-cookies .cc-cookie-accept"}],optOut:[{if:{exists:".cc-cookies .cc-cookie-decline"},then:[{click:".cc-cookies .cc-cookie-decline"}],else:[{hide:".cc-cookies"}]}]},{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:"dan-com",vendorUrl:"https://unknown",runContext:{main:!0,frame:!1},prehideSelectors:[],detectCmp:[{exists:".cookie-banner.show .cookie-banner__content-all-btn"}],detectPopup:[{visible:".cookie-banner.show .cookie-banner__content-all-btn"}],optIn:[{waitForThenClick:".cookie-banner__content-all-btn"}],optOut:[{waitForThenClick:".cookie-banner__content-essential-btn"}]},{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"] [class*=footer]'}],detectPopup:[{visible:'[data-project="mol-fe-cmp"] [class*=footer]'}],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"] [class*=footer]',"xpath///button[contains(., 'Save & Exit')]"]}]},{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:"dpgmedia-nl",prehideSelectors:["#pg-shadow-host"],detectCmp:[{exists:"#pg-shadow-host"}],detectPopup:[{visible:["#pg-shadow-host","#pg-modal"]}],optIn:[{waitForThenClick:["#pg-shadow-host","#pg-accept-btn"]}],optOut:[{waitForThenClick:["#pg-shadow-host","#pg-configure-btn"]},{waitForThenClick:["#pg-shadow-host","#pg-reject-btn"]}]},{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:"ebay",vendorUrl:"https://ebay.com",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?ebay\\.([.a-z]+)/"},prehideSelectors:["#gdpr-banner"],detectCmp:[{exists:"#gdpr-banner"}],detectPopup:[{visible:"#gdpr-banner"}],optIn:[{waitForThenClick:"#gdpr-banner-accept"}],optOut:[{waitForThenClick:"#gdpr-banner-decline"}]},{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:"#ensModalWrapper[style*=block]"}],optIn:[{waitForThenClick:"#modalAcceptButton"}],optOut:[{wait:500},{visible:"#ensModalWrapper[style*=block]"},{waitForThenClick:".ensCheckbox:checked",all:!0},{waitForThenClick:"#ensSave"}]},{name:"Ensighten ensNotifyBanner",prehideSelectors:["#ensNotifyBanner"],detectCmp:[{exists:"#ensNotifyBanner"}],detectPopup:[{visible:"#ensNotifyBanner[style*=block]"}],optIn:[{waitForThenClick:"#ensCloseBanner"}],optOut:[{wait:500},{visible:"#ensNotifyBanner[style*=block]"},{waitForThenClick:"#ensRejectAll,#rejectAll,#ensRejectBanner,.rejectAll,#ensCloseBanner",timeout:2e3}]},{name:"espace-personnel.agirc-arrco.fr",runContext:{urlPattern:"^https://espace-personnel\\.agirc-arrco\\.fr/"},prehideSelectors:[".cdk-overlay-container"],detectCmp:[{exists:".cdk-overlay-container app-esaa-cookie-component"}],detectPopup:[{visible:".cdk-overlay-container app-esaa-cookie-component"}],optIn:[{waitForThenClick:".btn-cookie-accepter"}],optOut:[{waitForThenClick:".btn-cookie-refuser"}]},{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"},{eval:"EVAL_FIDES_DETECT_POPUP"}],optIn:[{waitForThenClick:"#fides-banner .fides-accept-all-button"}],optOut:[{waitForThenClick:"#fides-banner .fides-reject-all-button"}]},{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:"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:"holidaymedia",vendorUrl:"https://holidaymedia.nl/",prehideSelectors:["dialog[data-cookie-consent]"],detectCmp:[{exists:"dialog[data-cookie-consent]"}],detectPopup:[{visible:"dialog[data-cookie-consent]"}],optIn:[{waitForThenClick:"button.cookie-consent__button--accept-all"}],optOut:[{waitForThenClick:'a[data-cookie-accept="functional"]',timeout:2e3}]},{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:[],detectCmp:[{exists:'xpath///span[contains(., "Vill du tillåta användningen av cookies från Instagram i den här webbläsaren?") or contains(., "Allow the use of cookies from Instagram on this browser?") or contains(., "Povolit v prohlížeči použití souborů cookie z Instagramu?") or contains(., "Dopustiti upotrebu kolačića s Instagrama na ovom pregledniku?") or contains(., "Разрешить использование файлов cookie от Instagram в этом браузере?") or contains(., "Vuoi consentire l\'uso dei cookie di Instagram su questo browser?") or contains(., "Povoliť používanie cookies zo služby Instagram v tomto prehliadači?") or contains(., "Die Verwendung von Cookies durch Instagram in diesem Browser erlauben?") or contains(., "Sallitaanko Instagramin evästeiden käyttö tällä selaimella?") or contains(., "Engedélyezed az Instagram cookie-jainak használatát ebben a böngészőben?") or contains(., "Het gebruik van cookies van Instagram toestaan in deze browser?") or contains(., "Bu tarayıcıda Instagram\'dan çerez kullanımına izin verilsin mi?") or contains(., "Permitir o uso de cookies do Instagram neste navegador?") or contains(., "Permiţi folosirea modulelor cookie de la Instagram în acest browser?") or contains(., "Autoriser l’utilisation des cookies d’Instagram sur ce navigateur ?") or contains(., "¿Permitir el uso de cookies de Instagram en este navegador?") or contains(., "Zezwolić na użycie plików cookie z Instagramu w tej przeglądarce?") or contains(., "Να επιτρέπεται η χρήση cookies από τo Instagram σε αυτό το πρόγραμμα περιήγησης;") or contains(., "Разрешавате ли използването на бисквитки от Instagram на този браузър?") or contains(., "Vil du tillade brugen af cookies fra Instagram i denne browser?") or contains(., "Vil du tillate bruk av informasjonskapsler fra Instagram i denne nettleseren?")]'}],detectPopup:[{visible:'xpath///span[contains(., "Vill du tillåta användningen av cookies från Instagram i den här webbläsaren?") or contains(., "Allow the use of cookies from Instagram on this browser?") or contains(., "Povolit v prohlížeči použití souborů cookie z Instagramu?") or contains(., "Dopustiti upotrebu kolačića s Instagrama na ovom pregledniku?") or contains(., "Разрешить использование файлов cookie от Instagram в этом браузере?") or contains(., "Vuoi consentire l\'uso dei cookie di Instagram su questo browser?") or contains(., "Povoliť používanie cookies zo služby Instagram v tomto prehliadači?") or contains(., "Die Verwendung von Cookies durch Instagram in diesem Browser erlauben?") or contains(., "Sallitaanko Instagramin evästeiden käyttö tällä selaimella?") or contains(., "Engedélyezed az Instagram cookie-jainak használatát ebben a böngészőben?") or contains(., "Het gebruik van cookies van Instagram toestaan in deze browser?") or contains(., "Bu tarayıcıda Instagram\'dan çerez kullanımına izin verilsin mi?") or contains(., "Permitir o uso de cookies do Instagram neste navegador?") or contains(., "Permiţi folosirea modulelor cookie de la Instagram în acest browser?") or contains(., "Autoriser l’utilisation des cookies d’Instagram sur ce navigateur ?") or contains(., "¿Permitir el uso de cookies de Instagram en este navegador?") or contains(., "Zezwolić na użycie plików cookie z Instagramu w tej przeglądarce?") or contains(., "Να επιτρέπεται η χρήση cookies από τo Instagram σε αυτό το πρόγραμμα περιήγησης;") or contains(., "Разрешавате ли използването на бисквитки от Instagram на този браузър?") or contains(., "Vil du tillade brugen af cookies fra Instagram i denne browser?") or contains(., "Vil du tillate bruk av informasjonskapsler fra Instagram i denne nettleseren?")]'}],optIn:[{waitForThenClick:"xpath///button[contains(., 'Tillad alle cookies') or contains(., 'Alle Cookies erlauben') or contains(., 'Allow all cookies') or contains(., 'Разрешаване на всички бисквитки') or contains(., 'Tillåt alla cookies') or contains(., 'Povolit všechny soubory cookie') or contains(., 'Tüm çerezlere izin ver') or contains(., 'Permite toate modulele cookie') or contains(., 'Να επιτρέπονται όλα τα cookies') or contains(., 'Tillat alle informasjonskapsler') or contains(., 'Povoliť všetky cookies') or contains(., 'Permitir todas las cookies') or contains(., 'Permitir todos os cookies') or contains(., 'Alle cookies toestaan') or contains(., 'Salli kaikki evästeet') or contains(., 'Consenti tutti i cookie') or contains(., 'Az összes cookie engedélyezése') or contains(., 'Autoriser tous les cookies') or contains(., 'Zezwól na wszystkie pliki cookie') or contains(., 'Разрешить все cookie') or contains(., 'Dopusti sve kolačiće')]"}],optOut:[{waitForThenClick:"xpath///button[contains(., 'Отклонить необязательные файлы cookie') or contains(., 'Decline optional cookies') or contains(., 'Refuser les cookies optionnels') or contains(., 'Hylkää valinnaiset evästeet') or contains(., 'Afvis valgfrie cookies') or contains(., 'Odmietnuť nepovinné cookies') or contains(., 'Απόρριψη προαιρετικών cookies') or contains(., 'Neka valfria cookies') or contains(., 'Optionale Cookies ablehnen') or contains(., 'Rifiuta cookie facoltativi') or contains(., 'Odbij neobavezne kolačiće') or contains(., 'Avvis valgfrie informasjonskapsler') or contains(., 'İsteğe bağlı çerezleri reddet') or contains(., 'Recusar cookies opcionais') or contains(., 'Optionele cookies afwijzen') or contains(., 'Rechazar cookies opcionales') or contains(., 'Odrzuć opcjonalne pliki cookie') or contains(., 'Отхвърляне на бисквитките по избор') or contains(., 'Odmítnout volitelné soubory cookie') or contains(., 'Refuză modulele cookie opţionale') or contains(., 'A nem kötelező cookie-k elutasítása')]"},{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:[{waitForThenClick:".iubenda-cs-accept-btn"}],optOut:[{waitForThenClick:".iubenda-cs-customize-btn"},{eval:"EVAL_IUBENDA_0"},{waitForThenClick:"#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], #lanyard_root button[class*=buttons-secondary]",comment:"can be either settings or reject button"}]},{waitFor:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description], #ketch-preferences",timeout:1e3,optional:!0},{if:{exists:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description], #ketch-preferences"},then:[{waitForThenClick:"#lanyard_root button[class*=rejectButton], #lanyard_root button[class*=rejectAllButton]"},{click:"#lanyard_root button[class*=confirmButton],#lanyard_root div[class*=actions_] > button:nth-child(1), #lanyard_root button[class*=actionButton]"}]}],test:[{eval:"EVAL_KETCH_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:"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:"medium",vendorUrl:"https://medium.com",cosmetic:!0,runContext:{main:!0,frame:!1,urlPattern:"^https://([a-z0-9-]+\\.)?medium\\.com/"},prehideSelectors:[],detectCmp:[{exists:'div:has(> div > div > div[role=alert] > a[href^="https://policy.medium.com/medium-privacy-policy-"])'}],detectPopup:[{visible:'div:has(> div > div > div[role=alert] > a[href^="https://policy.medium.com/medium-privacy-policy-"])'}],optIn:[{waitForThenClick:"[data-testid=close-button]"}],optOut:[{hide:'div:has(> div > div > div[role=alert] > a[href^="https://policy.medium.com/medium-privacy-policy-"])'}]},{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:"netbeat.de",runContext:{urlPattern:"^https://(www\\.)?netbeat\\.de/"},prehideSelectors:["div#cookieWarning"],detectCmp:[{exists:"div#cookieWarning"}],detectPopup:[{visible:"div#cookieWarning"}],optIn:[{waitForThenClick:"a#btnCookiesAcceptAll"}],optOut:[{waitForThenClick:"a#btnCookiesDenyAll"}]},{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:"nike",vendorUrl:"https://nike.com",runContext:{urlPattern:"^https://(www\\.)?nike\\.com/"},prehideSelectors:[],detectCmp:[{exists:"[data-testid=cookie-dialog-root]"}],detectPopup:[{visible:"[data-testid=cookie-dialog-root]"}],optIn:[{waitForThenClick:"[data-testid=dialog-accept-button]"}],optOut:[{waitForThenClick:"input[type=radio][id$=-declineLabel]",all:!0},{waitForThenClick:"[data-testid=confirm-choice-button]"}]},{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:"openai",vendorUrl:"https://platform.openai.com/",cosmetic:!1,runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?openai\\.com/"},prehideSelectors:["[data-testid=cookie-consent-banner]"],detectCmp:[{exists:"[data-testid=cookie-consent-banner]"}],detectPopup:[{visible:"[data-testid=cookie-consent-banner]"}],optIn:[{waitForThenClick:"xpath///button[contains(., 'Accept all')]"}],optOut:[{waitForThenClick:"xpath///button[contains(., 'Reject all')]"}],test:[{wait:500},{eval:"EVAL_OPENAI_TEST"}]},{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:"#bannerDeclineButton"},then:[{click:"#bannerDeclineButton"}],else:[{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:"pinterest-business",vendorUrl:"https://business.pinterest.com/",runContext:{urlPattern:"^https://.*\\.pinterest\\.com/"},prehideSelectors:[".BusinessCookieConsent"],detectCmp:[{exists:".BusinessCookieConsent"}],detectPopup:[{visible:".BusinessCookieConsent [data-id=cookie-consent-banner-buttons]"}],optIn:[{waitForThenClick:"[data-id=cookie-consent-banner-buttons] > div:nth-child(1) button"}],optOut:[{waitForThenClick:"[data-id=cookie-consent-banner-buttons] > div:nth-child(2) button"}]},{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:[{waitFor:'.qc-cmp2-summary-buttons > button[mode="secondary"]',timeout:2e3},{if:{exists:'.qc-cmp2-summary-buttons > button[mode="secondary"]:nth-of-type(2)'},then:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]:nth-of-type(2)'}],else:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]:nth-of-type(1)'},{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:"roblox",vendorUrl:"https://roblox.com",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?roblox\\.com/"},prehideSelectors:[],detectCmp:[{exists:".cookie-banner-wrapper"}],detectPopup:[{visible:".cookie-banner-wrapper .cookie-banner"}],optIn:[{waitForThenClick:".cookie-banner-wrapper button.btn-cta-lg"}],optOut:[{waitForThenClick:".cookie-banner-wrapper button.btn-secondary-lg"}],test:[{eval:"EVAL_ROBLOX_TEST"}]},{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:"#rejectAllMain"}],optIn:[{click:"#acceptAllMain"}],optOut:[{click:"#rejectAllMain"}]},{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 #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://([a-z0-9-]+\\.)?temu\\.com/"},prehideSelectors:[],detectCmp:[{exists:'div > div > div > div > span[href*="/cookie-and-similar-technologies-policy.html"]'}],detectPopup:[{visible:'div > div > div > div > span[href*="/cookie-and-similar-technologies-policy.html"]'}],optIn:[{waitForThenClick:'div > div > div:has(> div > span[href*="/cookie-and-similar-technologies-policy.html"]) > [role=button]:nth-child(3)'}],optOut:[{if:{exists:"xpath///span[contains(., 'Alle afwijzen') or contains(., 'Reject all') or contains(., 'Tümünü reddet') or contains(., 'Odrzuć wszystko')]"},then:[{waitForThenClick:"xpath///span[contains(., 'Alle afwijzen') or contains(., 'Reject all') or contains(., 'Tümünü reddet') or contains(., 'Odrzuć wszystko')]"}],else:[{waitForThenClick:'div > div > div:has(> div > span[href*="/cookie-and-similar-technologies-policy.html"]) > [role=button]:nth-child(2)'}]}]},{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",prehideSelectors:[".cc_dialog.cc_css_reboot,.cc_overlay_lock"],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:[{if:{exists:".cc_dialog.cc_css_reboot .cc_b_cp"},then:[{click:".cc_dialog.cc_css_reboot .cc_b_cp"},{waitForVisible:".cookie-consent-preferences-dialog .cc_cp_f_save button"},{waitForThenClick:".cookie-consent-preferences-dialog .cc_cp_f_save button"}],else:[{hide:".cc_dialog.cc_css_reboot,.cc_overlay_lock"}]}]},{name:"tesla",vendorUrl:"https://tesla.com/",runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?tesla\\.com/"},prehideSelectors:[],detectCmp:[{exists:"#cookie_banner"}],detectPopup:[{visible:"#cookie_banner"}],optIn:[{waitForThenClick:"#tsla-accept-cookie"}],optOut:[{waitForThenClick:"#tsla-reject-cookie"}],test:[{eval:"EVAL_TESLA_TEST"}]},{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:"twcc",vendorUrl:"https://unknown",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:""},prehideSelectors:["#twcc__mechanism"],detectCmp:[{exists:"#twcc__mechanism .twcc__notice"}],detectPopup:[{visible:"#twcc__mechanism .twcc__notice"}],optIn:[{waitForThenClick:"#twcc__accept-button"}],optOut:[{waitForThenClick:"#twcc__decline-button"}],test:[{eval:"EVAL_TWCC_TEST"}]},{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|x)\\.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>button[role=button]>span) > div:last-child > button[role=button]:first-child'}],optOut:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>button[role=button]>span) > div:last-child > button[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,#usercentrics-cmp-ui"}],detectPopup:[{eval:"EVAL_USERCENTRICS_API_0"},{if:{exists:"#usercentrics-cmp-ui"},then:[{waitForVisible:"#usercentrics-cmp-ui",timeout:2e3}],else:[{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",runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?uswitch\\.com/"},prehideSelectors:[".ucb"],detectCmp:[{exists:".ucb-banner"}],detectPopup:[{visible:".ucb-banner"}],optIn:[{waitForThenClick:".ucb-banner .ucb-btn-accept"}],optOut:[{waitForThenClick:".ucb-banner .ucb-btn-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:[]},{name:"zentralruf-de",runContext:{urlPattern:"^https://(www\\.)?zentralruf\\.de"},prehideSelectors:["#cookie_modal_wrapper"],detectCmp:[{exists:"#cookie_modal_wrapper"}],detectPopup:[{visible:"#cookie_modal_wrapper"}],optIn:[{waitForThenClick:"#cookie_modal_wrapper #cookie_modal_button_consent_all"}],optOut:[{waitForThenClick:"#cookie_modal_wrapper #cookie_modal_button_choose"}]}],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=[],i=[];for(const e of t)e.isCosmetic?i.push(e):o.push(e);let c=!1,n=await this.detectPopups(o,(async e=>{c=await this.handlePopup(e)}));if(0===n.length&&(n=await this.detectPopups(i,(async e=>{c=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 c}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 i=await Promise.allSettled(o),c=[];for(const e of i)"fulfilled"===e.status&&c.push(e.value);return c}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 i=this.config.logs;i.lifecycle&&console.log("checking if popup is open...",e.name);const c=await e.detectPopup().catch((t=>(i.errors&&console.warn(`error detecting popup for ${e.name}`,t),!1)));return!c&&t>0?(await this.domActions.wait(o),this.waitForPopup(e,t-1,o)):(i.lifecycle&&console.log(e.name,"popup is "+(c?"open":"not open")),c)}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)}}(); +!function(){"use strict";var e=class e{static setBase(t){e.base=t}static findElement(t,o=null,i=!1){let c=null;return c=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&&(c=c.filter((e=>{const o=e.textContent.toLowerCase();if(Array.isArray(t.textFilter)){let e=!1;for(const i of t.textFilter)if(-1!==o.indexOf(i.toLowerCase())){e=!0;break}return e}if(null!=t.textFilter)return-1!==o.indexOf(t.textFilter.toLowerCase())}))),null!=t.styleFilters&&(c=c.filter((e=>{const o=window.getComputedStyle(e);let i=!0;for(const e of t.styleFilters){const t=o[e.option];i=e.negated?i&&t!==e.value:i&&t===e.value}return i}))),null!=t.displayFilter&&(c=c.filter((e=>t.displayFilter?0!==e.offsetHeight:0===e.offsetHeight))),null!=t.iframeFilter&&(c=c.filter((()=>t.iframeFilter?window.location!==window.parent.location:window.location===window.parent.location))),null!=t.childFilter&&(c=c.filter((o=>{const i=e.base;e.setBase(o);const c=e.find(t.childFilter);return e.setBase(i),null!=c.target}))),i?c:(c.length>1&&console.warn("Multiple possible targets: ",c,t,o),c[0])}static find(t,o=!1){const i=[];if(null!=t.parent){const c=e.findElement(t.parent,null,o);if(null!=c){if(c instanceof Array)return c.forEach((c=>{const n=e.findElement(t.target,c,o);n instanceof Array?n.forEach((e=>{i.push({parent:c,target:e})})):i.push({parent:c,target:n})})),i;{const n=e.findElement(t.target,c,o);n instanceof Array?n.forEach((e=>{i.push({parent:c,target:e})})):i.push({parent:c,target:n})}}}else{const c=e.findElement(t.target,null,o);c instanceof Array?c.forEach((e=>{i.push({parent:null,target:e})})):i.push({parent:null,target:c})}return 0===i.length&&i.push({parent:null,target:null}),o?i:(1!==i.length&&console.warn("Multiple results found, even though multiple false",i),i[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 i(e,a){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return n(c)}(e);case"list":return async function(e,t){for(const o of e.actions)await i(o,t)}(e,a);case"consent":return async function(e,t){for(const c of e.consents){const e=-1!==t.indexOf(c.type);if(c.matcher&&c.toggleAction){o(c.matcher)!==e&&await i(c.toggleAction)}else e?await i(c.trueAction):await i(c.falseAction)}}(e,a);case"ifcss":return async function(e,o){const c=t.find(e);c.target?e.falseAction&&await i(e.falseAction,o):e.trueAction&&await i(e.trueAction,o)}(e,a);case"waitcss":return async function(e){await new Promise((o=>{let i=e.retries||10;const c=e.waitTime||250,n=()=>{const a=t.find(e);(e.negated&&a.target||!e.negated&&!a.target)&&i>0?(i-=1,setTimeout(n,c)):o()};n()}))}(e);case"foreach":return async function(e,o){const c=t.find(e,!0),n=t.base;for(const n of c)n.target&&(t.setBase(n.target),await i(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),i=t.find(e.dragTarget);if(o.target){const e=o.target.getBoundingClientRect(),t=i.target.getBoundingClientRect();let c=t.top-e.top,n=t.left-e.left;"y"===this.config.axis.toLowerCase()&&(n=0),"x"===this.config.axis.toLowerCase()&&(c=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+c,r+n,l+c,!1,!1,!1,!1,0,o.target);const u=document.createEvent("MouseEvents");u.initMouseEvent("mouseup",!0,!0,window,0,a+n,s+c,r+n,l+c,!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 c=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_KLARO_OPEN_POPUP:()=>{klaro.show(void 0,!0)},EVAL_KLARO_TRY_API_OPT_OUT:()=>{if(window.klaro&&"function"==typeof klaro.show&&"function"==typeof klaro.getManager)try{return klaro.getManager().changeAll(!1),klaro.getManager().saveAndApplyConsents(),!0}catch(e){return console.warn(e),!1}return!1},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_TRUSTARC_FRAME_TEST:()=>window&&window.QueryString&&"0"===window.QueryString.preferences,EVAL_TRUSTARC_FRAME_GTM:()=>window&&window.QueryString&&"1"===window.QueryString.gtm,EVAL_ABC_TEST:()=>document.cookie.includes("trackingconsent"),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_FIDES_DETECT_POPUP:()=>window.Fides?.initialized,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_KETCH_TEST:()=>document.cookie.includes("_ketch_consent_v1_"),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_OPENAI_TEST:()=>document.cookie.includes("oai-allow-ne=false"),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_ROBLOX_TEST:()=>document.cookie.includes("RBXcb"),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_TESLA_TEST:()=>document.cookie.includes("tsla-cookie-consent=rejected"),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_TWCC_TEST:()=>document.cookie.includes("twCookieConsent="),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 i=!1;try{i=!!t.call(globalThis)}catch(t){o.evals&&console.error("error evaluating rule",e,t)}return Promise.resolve(i)}const i=`(${t.toString()})()`;return o.evals&&console.log("async eval:",e,i),function(e,t){const o=a();r.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const i=new s(o);return r.pending.set(i.id,i),i.promise}(i,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 i=await this.evaluateRuleStep(e.if);o.rulesteps&&console.log("Condition is",i),i?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)||i(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 i=`${t} { ${"opacity"===o?"opacity: 0":"display: none"} !important; z-index: -1 !important; pointer-events: none !important; } `;return e instanceof HTMLStyleElement&&(e.innerText+=i,t.length>0)}async function b(e,t,o){const i=await e();return!i&&t>0?new Promise((i=>{setTimeout((async()=>{i(b(e,t-1,o))}),o)})):Promise.resolve(i)}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=(i=t,globalThis.structuredClone?structuredClone(i):JSON.parse(JSON.stringify(i)));var i;for(const i of Object.keys(t))void 0!==e[i]&&(o[i]=e[i]);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!0}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.wait(500),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!0}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(){if(await this.mainWorldEval("EVAL_TRUSTARC_FRAME_TEST"))return!0;let e=3e3;return await this.mainWorldEval("EVAL_TRUSTARC_FRAME_GTM")&&(e=1500),await b((()=>"complete"===document.readyState),20,100),await this.waitForElement(".mainContent[aria-hidden=false]",e),!!this.click(".rejectAll")||(this.elementExists(".prefPanel")&&await this.waitForElement('.prefPanel[style="visibility: visible;"]',e),this.click("#catDetails0")?(this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",e),!0):this.click(".required")?(this.waitForThenClick("#gwt-debug-close_id",e),!0):(await this.navigateToSettings(),this.click(".switch span:nth-child(1):not(.active)",!0),this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",10*e),!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}async test(){return await this.wait(500),await this.mainWorldEval("EVAL_TRUSTARC_FRAME_TEST")}},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.waitForVisible(e),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,#_evidon-background"),await this.waitForThenClick("#_evidon-option-button"),await this.waitForElement("#evidon-prefdiag-overlay",5e3),await this.wait(500),await this.waitForThenClick("#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!!await this.mainWorldEval("EVAL_KLARO_TRY_API_OPT_OUT")||(!!this.click(".klaro .cn-decline")||(await this.mainWorldEval("EVAL_KLARO_OPEN_POPUP"),!!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)}},class extends d{constructor(){super(...arguments),this.name="Admiral"}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("div > div[class*=Card] > div[class*=Frame] > div[class*=Pills] > button[class*=Pills__StyledPill]")}async detectPopup(){return this.elementVisible("div > div[class*=Card] > div[class*=Frame] > div[class*=Pills] > button[class*=Pills__StyledPill]","any")}async optOut(){const e="xpath///button[contains(., 'Afvis alle') or contains(., 'Reject all') or contains(., 'Odbaci sve') or contains(., 'Rechazar todo') or contains(., 'Atmesti visus') or contains(., 'Odmítnout vše') or contains(., 'Απόρριψη όλων') or contains(., 'Rejeitar tudo') or contains(., 'Tümünü reddet') or contains(., 'Отклонить все') or contains(., 'Noraidīt visu') or contains(., 'Avvisa alla') or contains(., 'Odrzuć wszystkie') or contains(., 'Alles afwijzen') or contains(., 'Отхвърляне на всички') or contains(., 'Rifiuta tutto') or contains(., 'Zavrni vse') or contains(., 'Az összes elutasítása') or contains(., 'Respingeți tot') or contains(., 'Alles ablehnen') or contains(., 'Tout rejeter') or contains(., 'Odmietnuť všetko') or contains(., 'Lükka kõik tagasi') or contains(., 'Hylkää kaikki')]";if(await this.waitForElement(e,500))return this.click(e);const t="xpath///button[contains(., 'Spara & avsluta') or contains(., 'Save & exit') or contains(., 'Uložit a ukončit') or contains(., 'Enregistrer et quitter') or contains(., 'Speichern & Verlassen') or contains(., 'Tallenna ja poistu') or contains(., 'Išsaugoti ir išeiti') or contains(., 'Opslaan & afsluiten') or contains(., 'Guardar y salir') or contains(., 'Shrani in zapri') or contains(., 'Uložiť a ukončiť') or contains(., 'Kaydet ve çıkış yap') or contains(., 'Сохранить и выйти') or contains(., 'Salvesta ja välju') or contains(., 'Salva ed esci') or contains(., 'Gem & afslut') or contains(., 'Αποθήκευση και έξοδος') or contains(., 'Saglabāt un iziet') or contains(., 'Mentés és kilépés') or contains(., 'Guardar e sair') or contains(., 'Zapisz & zakończ') or contains(., 'Salvare și ieșire') or contains(., 'Spremi i izađi') or contains(., 'Запазване и изход')]";if(await this.waitForThenClick("xpath///button[contains(., 'Zwecke') or contains(., 'Σκοποί') or contains(., 'Purposes') or contains(., 'Цели') or contains(., 'Eesmärgid') or contains(., 'Tikslai') or contains(., 'Svrhe') or contains(., 'Cele') or contains(., 'Účely') or contains(., 'Finalidades') or contains(., 'Mērķi') or contains(., 'Scopuri') or contains(., 'Fines') or contains(., 'Ändamål') or contains(., 'Finalités') or contains(., 'Doeleinden') or contains(., 'Tarkoitukset') or contains(., 'Scopi') or contains(., 'Amaçlar') or contains(., 'Nameni') or contains(., 'Célok') or contains(., 'Formål')]")&&await this.waitForVisible(t)){return this.elementSelector(t)[0].parentElement.parentElement.querySelectorAll("input[type=checkbox]:checked").forEach((e=>e.click())),this.click(t)}return!1}async optIn(){return this.click("xpath///button[contains(., 'Sprejmi vse') or contains(., 'Prihvati sve') or contains(., 'Godkänn alla') or contains(., 'Prijať všetko') or contains(., 'Принять все') or contains(., 'Aceptar todo') or contains(., 'Αποδοχή όλων') or contains(., 'Zaakceptuj wszystkie') or contains(., 'Accetta tutto') or contains(., 'Priimti visus') or contains(., 'Pieņemt visu') or contains(., 'Tümünü kabul et') or contains(., 'Az összes elfogadása') or contains(., 'Accept all') or contains(., 'Приемане на всички') or contains(., 'Accepter alle') or contains(., 'Hyväksy kaikki') or contains(., 'Tout accepter') or contains(., 'Alles accepteren') or contains(., 'Aktsepteeri kõik') or contains(., 'Přijmout vše') or contains(., 'Alles akzeptieren') or contains(., 'Aceitar tudo') or contains(., 'Acceptați tot')]")}}],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),i=new Array(o.length);return o.forEach(((e,t)=>{i[t]=_(e)})),"none"===t?i.every((e=>!e)):0!==i.length&&("any"===t?i.some((e=>e)):i.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"){const i=Math.ceil(t/200);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[waitForVisible]",e),b((()=>this.elementVisible(e,o)),i,200)}async waitForThenClick(e,t=1e4,o=!1){return await this.waitForElement(e,t),this.click(e,o)}wait(e){return this.autoconsentInstance.config.logs.rulesteps&&console.log("[wait]",e),new Promise((t=>{setTimeout((()=>{t(!0)}),e)}))}hide(e,t){this.autoconsentInstance.config.logs.rulesteps&&console.log("[hide]",e);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),i=document.evaluate(o,t,null,XPathResult.ANY_TYPE,null);let c=null;const n=[];for(;c=i.iterateNext();)n.push(c);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 i of e){if(t=this.querySingleReplySelector(i,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:"aa",vendorUrl:"https://aa.com",prehideSelectors:[],cosmetic:!0,detectCmp:[{exists:"#aa_optoutmulti-Modal,#cookieBannerMessage"}],detectPopup:[{visible:"#aa_optoutmulti-Modal,#cookieBannerMessage"}],optIn:[{hide:"#aa_optoutmulti-Modal,#cookieBannerMessage"},{waitForThenClick:"#aa_optoutmulti_checkBox"},{waitForThenClick:"#aa_optoutmulti-Modal button.optoutmulti_button"}],optOut:[{hide:"#aa_optoutmulti-Modal,#cookieBannerMessage"}]},{name:"abc",vendorUrl:"https://abc.net.au",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?abc\\.net\\.au/"},prehideSelectors:[],detectCmp:[{exists:"[data-component=CookieBanner]"}],detectPopup:[{visible:"[data-component=CookieBanner] [data-component=CookieBanner_AcceptAll]"}],optIn:[{waitForThenClick:"[data-component=CookieBanner] [data-component=CookieBanner_AcceptAll]"}],optOut:[{waitForThenClick:"[data-component=CookieBanner] [data-component=CookieBanner_AcceptABCRequired]"}],test:[{eval:"EVAL_ABC_TEST"}]},{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:"acris",prehideSelectors:["div.acris-cookie-consent"],detectCmp:[{exists:"[data-acris-cookie-consent]"}],detectPopup:[{visible:".acris-cookie-consent.is--modal"}],optIn:[{waitForVisible:"#ccConsentAcceptAllButton",check:"any"},{wait:500},{waitForThenClick:"#ccConsentAcceptAllButton"}],optOut:[{waitForVisible:"#ccAcceptOnlyFunctional",check:"any"},{wait:500},{waitForThenClick:"#ccAcceptOnlyFunctional"}]},{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,#voyager-gdpr > div"}],detectPopup:[{visible:"#gdpr-new-container,#voyager-gdpr > div"}],optIn:[{waitForThenClick:"#gdpr-new-container .btn-accept,#voyager-gdpr > div > div > button:nth-child(1)"}],optOut:[{if:{exists:"#voyager-gdpr > div"},then:[{waitForThenClick:"#voyager-gdpr > div > div > button:nth-child(2)"}],else:[{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:"amex",vendorUrl:"https://www.americanexpress.com/",cosmetic:!1,prehideSelectors:["#user-consent-management-granular-banner-overlay"],detectCmp:[{exists:"#user-consent-management-granular-banner-overlay"}],detectPopup:[{visible:"#user-consent-management-granular-banner-overlay"}],optIn:[{waitForThenClick:"[data-testid=granular-banner-button-accept-all]"}],optOut:[{waitForThenClick:"[data-testid=granular-banner-button-decline-all]"}]},{name:"aquasana.com",prehideSelectors:["#consent-tracking"],detectCmp:[{exists:"#consent-tracking"}],detectPopup:[{exists:"#consent-tracking"}],optIn:[{waitForThenClick:"#consent-tracking .affirm.btn"}],optOut:[{if:{exists:"#consent-tracking .decline.btn"},then:[{click:"#consent-tracking .decline.btn"}],else:[{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:"check24-partnerprogramm-de",prehideSelectors:["[data-modal-content]:has([data-toggle-target^='cookie'])"],detectCmp:[{exists:"[data-toggle-target^='cookie']"}],detectPopup:[{visible:"[data-toggle-target^='cookie']",check:"any"}],optIn:[{waitForThenClick:"[data-cookie-accept-all]"}],optOut:[{waitForThenClick:"[data-cookie-dismiss-all]"}]},{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:[{if:{exists:".cmp-pref-link"},then:[{click:".cmp-pref-link"},{waitForThenClick:".cmp-body [id*=rejectAll]"},{waitForThenClick:".cmp-body .cmp-save-btn"}]}]}]},{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:"cookiecuttr",vendorUrl:"https://github.com/cdwharton/cookieCuttr",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:""},prehideSelectors:[".cc-cookies"],detectCmp:[{exists:".cc-cookies .cc-cookie-accept"}],detectPopup:[{visible:".cc-cookies .cc-cookie-accept"}],optIn:[{waitForThenClick:".cc-cookies .cc-cookie-accept"}],optOut:[{if:{exists:".cc-cookies .cc-cookie-decline"},then:[{click:".cc-cookies .cc-cookie-decline"}],else:[{hide:".cc-cookies"}]}]},{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:"dan-com",vendorUrl:"https://unknown",runContext:{main:!0,frame:!1},prehideSelectors:[],detectCmp:[{exists:".cookie-banner.show .cookie-banner__content-all-btn"}],detectPopup:[{visible:".cookie-banner.show .cookie-banner__content-all-btn"}],optIn:[{waitForThenClick:".cookie-banner__content-all-btn"}],optOut:[{waitForThenClick:".cookie-banner__content-essential-btn"}]},{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"] [class*=footer]'}],detectPopup:[{visible:'[data-project="mol-fe-cmp"] [class*=footer]'}],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"] [class*=footer]',"xpath///button[contains(., 'Save & Exit')]"]}]},{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:"dpgmedia-nl",prehideSelectors:["#pg-shadow-host"],detectCmp:[{exists:"#pg-shadow-host"}],detectPopup:[{visible:["#pg-shadow-host","#pg-modal"]}],optIn:[{waitForThenClick:["#pg-shadow-host","#pg-accept-btn"]}],optOut:[{waitForThenClick:["#pg-shadow-host","#pg-configure-btn"]},{waitForThenClick:["#pg-shadow-host","#pg-reject-btn"]}]},{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:"ebay",vendorUrl:"https://ebay.com",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?ebay\\.([.a-z]+)/"},prehideSelectors:["#gdpr-banner"],detectCmp:[{exists:"#gdpr-banner"}],detectPopup:[{visible:"#gdpr-banner"}],optIn:[{waitForThenClick:"#gdpr-banner-accept"}],optOut:[{waitForThenClick:"#gdpr-banner-decline"}]},{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:"#ensModalWrapper[style*=block]"}],optIn:[{waitForThenClick:"#modalAcceptButton"}],optOut:[{wait:500},{visible:"#ensModalWrapper[style*=block]"},{waitForThenClick:".ensCheckbox:checked",all:!0},{waitForThenClick:"#ensSave"}]},{name:"Ensighten ensNotifyBanner",prehideSelectors:["#ensNotifyBanner"],detectCmp:[{exists:"#ensNotifyBanner"}],detectPopup:[{visible:"#ensNotifyBanner[style*=block]"}],optIn:[{waitForThenClick:"#ensCloseBanner"}],optOut:[{wait:500},{visible:"#ensNotifyBanner[style*=block]"},{waitForThenClick:"#ensRejectAll,#rejectAll,#ensRejectBanner,.rejectAll,#ensCloseBanner",timeout:2e3}]},{name:"espace-personnel.agirc-arrco.fr",runContext:{urlPattern:"^https://espace-personnel\\.agirc-arrco\\.fr/"},prehideSelectors:[".cdk-overlay-container"],detectCmp:[{exists:".cdk-overlay-container app-esaa-cookie-component"}],detectPopup:[{visible:".cdk-overlay-container app-esaa-cookie-component"}],optIn:[{waitForThenClick:".btn-cookie-accepter"}],optOut:[{waitForThenClick:".btn-cookie-refuser"}]},{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"},{eval:"EVAL_FIDES_DETECT_POPUP"}],optIn:[{waitForThenClick:"#fides-banner .fides-accept-all-button"}],optOut:[{waitForThenClick:"#fides-banner .fides-reject-all-button"}]},{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:"google-consent-standalone",prehideSelectors:[],detectCmp:[{exists:'a[href^="https://policies.google.com/technologies/cookies"'},{exists:'form[action^="https://consent.google."][action$="/save"]'}],detectPopup:[{visible:'a[href^="https://policies.google.com/technologies/cookies"'}],optIn:[{waitForThenClick:'form[action^="https://consent.google."][action$="/save"]:has(input[name=set_eom][value=false]) button'}],optOut:[{waitForThenClick:'form[action^="https://consent.google."][action$="/save"]:has(input[name=set_eom][value=true]) button'}]},{name:"google-cookiebar",vendorUrl:"https://www.android.com/better-together/quick-share-app/",cosmetic:!1,prehideSelectors:[".glue-cookie-notification-bar"],detectCmp:[{exists:".glue-cookie-notification-bar"}],detectPopup:[{visible:".glue-cookie-notification-bar"}],optIn:[{waitForThenClick:".glue-cookie-notification-bar__accept"}],optOut:[{if:{exists:".glue-cookie-notification-bar__reject"},then:[{click:".glue-cookie-notification-bar__reject"}],else:[{hide:".glue-cookie-notification-bar"}]}],test:[]},{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:"holidaymedia",vendorUrl:"https://holidaymedia.nl/",prehideSelectors:["dialog[data-cookie-consent]"],detectCmp:[{exists:"dialog[data-cookie-consent]"}],detectPopup:[{visible:"dialog[data-cookie-consent]"}],optIn:[{waitForThenClick:"button.cookie-consent__button--accept-all"}],optOut:[{waitForThenClick:'a[data-cookie-accept="functional"]',timeout:2e3}]},{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:[],detectCmp:[{exists:'xpath///span[contains(., "Vill du tillåta användningen av cookies från Instagram i den här webbläsaren?") or contains(., "Allow the use of cookies from Instagram on this browser?") or contains(., "Povolit v prohlížeči použití souborů cookie z Instagramu?") or contains(., "Dopustiti upotrebu kolačića s Instagrama na ovom pregledniku?") or contains(., "Разрешить использование файлов cookie от Instagram в этом браузере?") or contains(., "Vuoi consentire l\'uso dei cookie di Instagram su questo browser?") or contains(., "Povoliť používanie cookies zo služby Instagram v tomto prehliadači?") or contains(., "Die Verwendung von Cookies durch Instagram in diesem Browser erlauben?") or contains(., "Sallitaanko Instagramin evästeiden käyttö tällä selaimella?") or contains(., "Engedélyezed az Instagram cookie-jainak használatát ebben a böngészőben?") or contains(., "Het gebruik van cookies van Instagram toestaan in deze browser?") or contains(., "Bu tarayıcıda Instagram\'dan çerez kullanımına izin verilsin mi?") or contains(., "Permitir o uso de cookies do Instagram neste navegador?") or contains(., "Permiţi folosirea modulelor cookie de la Instagram în acest browser?") or contains(., "Autoriser l’utilisation des cookies d’Instagram sur ce navigateur ?") or contains(., "¿Permitir el uso de cookies de Instagram en este navegador?") or contains(., "Zezwolić na użycie plików cookie z Instagramu w tej przeglądarce?") or contains(., "Να επιτρέπεται η χρήση cookies από τo Instagram σε αυτό το πρόγραμμα περιήγησης;") or contains(., "Разрешавате ли използването на бисквитки от Instagram на този браузър?") or contains(., "Vil du tillade brugen af cookies fra Instagram i denne browser?") or contains(., "Vil du tillate bruk av informasjonskapsler fra Instagram i denne nettleseren?")]'}],detectPopup:[{visible:'xpath///span[contains(., "Vill du tillåta användningen av cookies från Instagram i den här webbläsaren?") or contains(., "Allow the use of cookies from Instagram on this browser?") or contains(., "Povolit v prohlížeči použití souborů cookie z Instagramu?") or contains(., "Dopustiti upotrebu kolačića s Instagrama na ovom pregledniku?") or contains(., "Разрешить использование файлов cookie от Instagram в этом браузере?") or contains(., "Vuoi consentire l\'uso dei cookie di Instagram su questo browser?") or contains(., "Povoliť používanie cookies zo služby Instagram v tomto prehliadači?") or contains(., "Die Verwendung von Cookies durch Instagram in diesem Browser erlauben?") or contains(., "Sallitaanko Instagramin evästeiden käyttö tällä selaimella?") or contains(., "Engedélyezed az Instagram cookie-jainak használatát ebben a böngészőben?") or contains(., "Het gebruik van cookies van Instagram toestaan in deze browser?") or contains(., "Bu tarayıcıda Instagram\'dan çerez kullanımına izin verilsin mi?") or contains(., "Permitir o uso de cookies do Instagram neste navegador?") or contains(., "Permiţi folosirea modulelor cookie de la Instagram în acest browser?") or contains(., "Autoriser l’utilisation des cookies d’Instagram sur ce navigateur ?") or contains(., "¿Permitir el uso de cookies de Instagram en este navegador?") or contains(., "Zezwolić na użycie plików cookie z Instagramu w tej przeglądarce?") or contains(., "Να επιτρέπεται η χρήση cookies από τo Instagram σε αυτό το πρόγραμμα περιήγησης;") or contains(., "Разрешавате ли използването на бисквитки от Instagram на този браузър?") or contains(., "Vil du tillade brugen af cookies fra Instagram i denne browser?") or contains(., "Vil du tillate bruk av informasjonskapsler fra Instagram i denne nettleseren?")]'}],optIn:[{waitForThenClick:"xpath///button[contains(., 'Tillad alle cookies') or contains(., 'Alle Cookies erlauben') or contains(., 'Allow all cookies') or contains(., 'Разрешаване на всички бисквитки') or contains(., 'Tillåt alla cookies') or contains(., 'Povolit všechny soubory cookie') or contains(., 'Tüm çerezlere izin ver') or contains(., 'Permite toate modulele cookie') or contains(., 'Να επιτρέπονται όλα τα cookies') or contains(., 'Tillat alle informasjonskapsler') or contains(., 'Povoliť všetky cookies') or contains(., 'Permitir todas las cookies') or contains(., 'Permitir todos os cookies') or contains(., 'Alle cookies toestaan') or contains(., 'Salli kaikki evästeet') or contains(., 'Consenti tutti i cookie') or contains(., 'Az összes cookie engedélyezése') or contains(., 'Autoriser tous les cookies') or contains(., 'Zezwól na wszystkie pliki cookie') or contains(., 'Разрешить все cookie') or contains(., 'Dopusti sve kolačiće')]"}],optOut:[{waitForThenClick:"xpath///button[contains(., 'Отклонить необязательные файлы cookie') or contains(., 'Decline optional cookies') or contains(., 'Refuser les cookies optionnels') or contains(., 'Hylkää valinnaiset evästeet') or contains(., 'Afvis valgfrie cookies') or contains(., 'Odmietnuť nepovinné cookies') or contains(., 'Απόρριψη προαιρετικών cookies') or contains(., 'Neka valfria cookies') or contains(., 'Optionale Cookies ablehnen') or contains(., 'Rifiuta cookie facoltativi') or contains(., 'Odbij neobavezne kolačiće') or contains(., 'Avvis valgfrie informasjonskapsler') or contains(., 'İsteğe bağlı çerezleri reddet') or contains(., 'Recusar cookies opcionais') or contains(., 'Optionele cookies afwijzen') or contains(., 'Rechazar cookies opcionales') or contains(., 'Odrzuć opcjonalne pliki cookie') or contains(., 'Отхвърляне на бисквитките по избор') or contains(., 'Odmítnout volitelné soubory cookie') or contains(., 'Refuză modulele cookie opţionale') or contains(., 'A nem kötelező cookie-k elutasítása')]"},{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:[{waitForThenClick:".iubenda-cs-accept-btn"}],optOut:[{waitForThenClick:".iubenda-cs-customize-btn"},{eval:"EVAL_IUBENDA_0"},{waitForThenClick:"#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], #lanyard_root button[class*=buttons-secondary]",comment:"can be either settings or reject button"}]},{waitFor:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description], #ketch-preferences",timeout:1e3,optional:!0},{if:{exists:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description], #ketch-preferences"},then:[{waitForThenClick:"#lanyard_root button[class*=rejectButton], #lanyard_root button[class*=rejectAllButton]"},{click:"#lanyard_root button[class*=confirmButton],#lanyard_root div[class*=actions_] > button:nth-child(1), #lanyard_root button[class*=actionButton]"}]}],test:[{eval:"EVAL_KETCH_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:"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:"medium",vendorUrl:"https://medium.com",cosmetic:!0,runContext:{main:!0,frame:!1,urlPattern:"^https://([a-z0-9-]+\\.)?medium\\.com/"},prehideSelectors:[],detectCmp:[{exists:'div:has(> div > div > div[role=alert] > a[href^="https://policy.medium.com/medium-privacy-policy-"])'}],detectPopup:[{visible:'div:has(> div > div > div[role=alert] > a[href^="https://policy.medium.com/medium-privacy-policy-"])'}],optIn:[{waitForThenClick:"[data-testid=close-button]"}],optOut:[{hide:'div:has(> div > div > div[role=alert] > a[href^="https://policy.medium.com/medium-privacy-policy-"])'}]},{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:"netbeat.de",runContext:{urlPattern:"^https://(www\\.)?netbeat\\.de/"},prehideSelectors:["div#cookieWarning"],detectCmp:[{exists:"div#cookieWarning"}],detectPopup:[{visible:"div#cookieWarning"}],optIn:[{waitForThenClick:"a#btnCookiesAcceptAll"}],optOut:[{waitForThenClick:"a#btnCookiesDenyAll"}]},{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:"nike",vendorUrl:"https://nike.com",runContext:{urlPattern:"^https://(www\\.)?nike\\.com/"},prehideSelectors:[],detectCmp:[{exists:"[data-testid=cookie-dialog-root]"}],detectPopup:[{visible:"[data-testid=cookie-dialog-root]"}],optIn:[{waitForThenClick:"[data-testid=dialog-accept-button]"}],optOut:[{waitForThenClick:"input[type=radio][id$=-declineLabel]",all:!0},{waitForThenClick:"[data-testid=confirm-choice-button]"}]},{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:"openai",vendorUrl:"https://platform.openai.com/",cosmetic:!1,runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?openai\\.com/"},prehideSelectors:["[data-testid=cookie-consent-banner]"],detectCmp:[{exists:"[data-testid=cookie-consent-banner]"}],detectPopup:[{visible:"[data-testid=cookie-consent-banner]"}],optIn:[{waitForThenClick:"xpath///button[contains(., 'Accept all')]"}],optOut:[{waitForThenClick:"xpath///button[contains(., 'Reject all')]"}],test:[{wait:500},{eval:"EVAL_OPENAI_TEST"}]},{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:[{visible:"#ccpaCookieBanner, .privacy-sheet-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"#bannerDeclineButton"},then:[{click:"#bannerDeclineButton"}],else:[{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:".cookieAction.saveCookie,.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:"pinterest-business",vendorUrl:"https://business.pinterest.com/",runContext:{urlPattern:"^https://.*\\.pinterest\\.com/"},prehideSelectors:[".BusinessCookieConsent"],detectCmp:[{exists:".BusinessCookieConsent"}],detectPopup:[{visible:".BusinessCookieConsent [data-id=cookie-consent-banner-buttons]"}],optIn:[{waitForThenClick:"[data-id=cookie-consent-banner-buttons] > div:nth-child(1) button"}],optOut:[{waitForThenClick:"[data-id=cookie-consent-banner-buttons] > div:nth-child(2) button"}]},{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:[{waitFor:'.qc-cmp2-summary-buttons > button[mode="secondary"]',timeout:2e3},{if:{exists:'.qc-cmp2-summary-buttons > button[mode="secondary"]:nth-of-type(2)'},then:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]:nth-of-type(2)'}],else:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]:nth-of-type(1)'},{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:"roblox",vendorUrl:"https://roblox.com",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?roblox\\.com/"},prehideSelectors:[],detectCmp:[{exists:".cookie-banner-wrapper"}],detectPopup:[{visible:".cookie-banner-wrapper .cookie-banner"}],optIn:[{waitForThenClick:".cookie-banner-wrapper button.btn-cta-lg"}],optOut:[{waitForThenClick:".cookie-banner-wrapper button.btn-secondary-lg"}],test:[{eval:"EVAL_ROBLOX_TEST"}]},{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:"#rejectAllMain"}],optIn:[{click:"#acceptAllMain"}],optOut:[{click:"#rejectAllMain"}]},{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 #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://([a-z0-9-]+\\.)?temu\\.com/"},prehideSelectors:[],detectCmp:[{exists:'div > div > div > div > span[href*="/cookie-and-similar-technologies-policy.html"]'}],detectPopup:[{visible:'div > div > div > div > span[href*="/cookie-and-similar-technologies-policy.html"]'}],optIn:[{waitForThenClick:'div > div > div:has(> div > span[href*="/cookie-and-similar-technologies-policy.html"]) > [role=button]:nth-child(3)'}],optOut:[{if:{exists:"xpath///span[contains(., 'Alle afwijzen') or contains(., 'Reject all') or contains(., 'Tümünü reddet') or contains(., 'Odrzuć wszystko')]"},then:[{waitForThenClick:"xpath///span[contains(., 'Alle afwijzen') or contains(., 'Reject all') or contains(., 'Tümünü reddet') or contains(., 'Odrzuć wszystko')]"}],else:[{waitForThenClick:'div > div > div:has(> div > span[href*="/cookie-and-similar-technologies-policy.html"]) > [role=button]:nth-child(2)'}]}]},{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",prehideSelectors:[".cc_dialog.cc_css_reboot,.cc_overlay_lock"],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:[{if:{exists:".cc_dialog.cc_css_reboot .cc_b_cp"},then:[{click:".cc_dialog.cc_css_reboot .cc_b_cp"},{waitForVisible:".cookie-consent-preferences-dialog .cc_cp_f_save button"},{waitForThenClick:".cookie-consent-preferences-dialog .cc_cp_f_save button"}],else:[{hide:".cc_dialog.cc_css_reboot,.cc_overlay_lock"}]}]},{name:"tesla",vendorUrl:"https://tesla.com/",runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?tesla\\.com/"},prehideSelectors:[],detectCmp:[{exists:"#cookie_banner"}],detectPopup:[{visible:"#cookie_banner"}],optIn:[{waitForThenClick:"#tsla-accept-cookie"}],optOut:[{waitForThenClick:"#tsla-reject-cookie"}],test:[{eval:"EVAL_TESLA_TEST"}]},{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:"twcc",vendorUrl:"https://unknown",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:""},prehideSelectors:["#twcc__mechanism"],detectCmp:[{exists:"#twcc__mechanism .twcc__notice"}],detectPopup:[{visible:"#twcc__mechanism .twcc__notice"}],optIn:[{waitForThenClick:"#twcc__accept-button"}],optOut:[{waitForThenClick:"#twcc__decline-button"}],test:[{eval:"EVAL_TWCC_TEST"}]},{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|x)\\.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>button[role=button]>span) > div:last-child > button[role=button]:first-child'}],optOut:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>button[role=button]>span) > div:last-child > button[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,#usercentrics-cmp-ui"}],detectPopup:[{eval:"EVAL_USERCENTRICS_API_0"},{if:{exists:"#usercentrics-cmp-ui"},then:[{waitForVisible:"#usercentrics-cmp-ui",timeout:2e3}],else:[{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",runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?uswitch\\.com/"},prehideSelectors:[".ucb"],detectCmp:[{exists:".ucb-banner"}],detectPopup:[{visible:".ucb-banner"}],optIn:[{waitForThenClick:".ucb-banner .ucb-btn-accept"}],optOut:[{waitForThenClick:".ucb-banner .ucb-btn-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:[]},{name:"zentralruf-de",runContext:{urlPattern:"^https://(www\\.)?zentralruf\\.de"},prehideSelectors:["#cookie_modal_wrapper"],detectCmp:[{exists:"#cookie_modal_wrapper"}],detectPopup:[{visible:"#cookie_modal_wrapper"}],optIn:[{waitForThenClick:"#cookie_modal_wrapper #cookie_modal_button_consent_all"}],optOut:[{waitForThenClick:"#cookie_modal_wrapper #cookie_modal_button_choose"}]}],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=[],i=[];for(const e of t)e.isCosmetic?i.push(e):o.push(e);let c=!1,n=await this.detectPopups(o,(async e=>{c=await this.handlePopup(e)}));if(0===n.length&&(n=await this.detectPopups(i,(async e=>{c=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 c}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 i=await Promise.allSettled(o),c=[];for(const e of i)"fulfilled"===e.status&&c.push(e.value);return c}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 i=this.config.logs;i.lifecycle&&console.log("checking if popup is open...",e.name);const c=await e.detectPopup().catch((t=>(i.errors&&console.warn(`error detecting popup for ${e.name}`,t),!1)));return!c&&t>0?(await this.domActions.wait(o),this.waitForPopup(e,t-1,o)):(i.lifecycle&&console.log(e.name,"popup is "+(c?"open":"not open")),c)}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/DuckDuckGo/BarsAnimator.swift b/DuckDuckGo/BarsAnimator.swift index 01cae59724..c52e7201d7 100644 --- a/DuckDuckGo/BarsAnimator.swift +++ b/DuckDuckGo/BarsAnimator.swift @@ -87,18 +87,38 @@ class BarsAnimator { } private func transitioningAndScrolling(in scrollView: UIScrollView) { - let ratio = calculateTransitionRatio(for: scrollView.contentOffset.y) + + // On iOS 18 we end up in a loop after setBarsVisibility. + // It seems to trigger a new didScrollEvent when rendering some PDF files + // That causes an infinite loop. + // Are viewDidScroll calls happening more often for PDF's on iOS 18? + // Adding a debouncer while we investigate further + // https://app.asana.com/0/1204099484721401/1208671955053442/f + let debounceDelay: TimeInterval = 0.01 + struct Debounce { + static var workItem: DispatchWorkItem? + } + Debounce.workItem?.cancel() - if ratio == 1.0 { - barsState = .hidden - } else if ratio == 0 { - barsState = .revealed - } else if transitionProgress == ratio { - return + Debounce.workItem = DispatchWorkItem { [weak self] in + guard let self = self else { return } + + let ratio = self.calculateTransitionRatio(for: scrollView.contentOffset.y) + + if ratio == 1.0 { + self.barsState = .hidden + } else if ratio == 0 { + self.barsState = .revealed + } else if self.transitionProgress == ratio { + return + } + + self.delegate?.setBarsVisibility(1.0 - ratio, animated: false) + self.transitionProgress = ratio } - delegate?.setBarsVisibility(1.0 - ratio, animated: false) - transitionProgress = ratio + // Schedule the work item + DispatchQueue.main.asyncAfter(deadline: .now() + debounceDelay, execute: Debounce.workItem!) } private func hiddenAndScrolling(in scrollView: UIScrollView) { diff --git a/DuckDuckGo/Base.lproj/Home.storyboard b/DuckDuckGo/Base.lproj/Home.storyboard deleted file mode 100644 index 0ac1f1d65c..0000000000 --- a/DuckDuckGo/Base.lproj/Home.storyboard +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift b/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift index 401993c4da..5fe45d94f5 100644 --- a/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift +++ b/DuckDuckGo/BrowsingMenu/BrowsingMenuAnimator.swift @@ -106,7 +106,7 @@ final class BrowsingMenuAnimator: NSObject, UIViewControllerAnimatedTransitionin fromViewController.view.isHidden = true - if toViewController.homeViewController != nil { + if toViewController.newTabPageViewController != nil { toViewController.presentedMenuButton.setState(.bookmarksImage, animated: true) } else { toViewController.presentedMenuButton.setState(.menuImage, animated: true) diff --git a/DuckDuckGo/DaxDialogViewController.swift b/DuckDuckGo/DaxDialogViewController.swift index e231e6c566..78c524c2f5 100644 --- a/DuckDuckGo/DaxDialogViewController.swift +++ b/DuckDuckGo/DaxDialogViewController.swift @@ -42,7 +42,7 @@ class DaxDialogViewController: UIViewController { initCTA() } } - + func calculateHeight() -> CGFloat { guard let text = message ?? cta, !text.isEmpty else { return 370.0 } @@ -59,7 +59,7 @@ class DaxDialogViewController: UIViewController { let bottomMargin: CGFloat = 24.0 return iconHeight + topMargin + size.height + buttonHeight + bottomMargin } - + var onTapCta: (() -> Void)? private var position: Int = 0 @@ -188,3 +188,14 @@ extension DaxDialogViewController { } } } + +extension DaxDialogViewController { + static func loadFromStoryboard() -> DaxDialogViewController { + let storyboard = UIStoryboard(name: "DaxOnboarding", bundle: Bundle.main) + guard let controller = storyboard.instantiateViewController(identifier: "DaxDialog") as? DaxDialogViewController else { + fatalError("Failed to instantiate DaxDialogViewController from storyboard") + } + + return controller + } +} diff --git a/DuckDuckGo/DaxOnboarding.xcassets/add-to-dock-gradient.imageset/Contents.json b/DuckDuckGo/DaxOnboarding.xcassets/add-to-dock-gradient.imageset/Contents.json new file mode 100644 index 0000000000..24f4eca3a7 --- /dev/null +++ b/DuckDuckGo/DaxOnboarding.xcassets/add-to-dock-gradient.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "add-to-dock-gradient.png", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/DaxOnboarding.xcassets/add-to-dock-gradient.imageset/add-to-dock-gradient.png b/DuckDuckGo/DaxOnboarding.xcassets/add-to-dock-gradient.imageset/add-to-dock-gradient.png new file mode 100644 index 0000000000..38ba5f9b49 Binary files /dev/null and b/DuckDuckGo/DaxOnboarding.xcassets/add-to-dock-gradient.imageset/add-to-dock-gradient.png differ diff --git a/DuckDuckGo/Debug.storyboard b/DuckDuckGo/Debug.storyboard index c66b2130dc..ebc359a9e2 100644 --- a/DuckDuckGo/Debug.storyboard +++ b/DuckDuckGo/Debug.storyboard @@ -374,32 +374,14 @@ - - - - - - - - - - + - - - - - - - - - - + - + @@ -750,7 +732,6 @@ - @@ -1049,34 +1030,34 @@ - + - + - + - + diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 55e9d907a5..486688dce2 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -26,7 +26,7 @@ import UserScript import Core import ContentScopeScripts -/// Values that the Frontend can use to determine the current state. +/// Values that the frontend can use to determine the current state. struct InitialPlayerSettings: Codable { struct PlayerSettings: Codable { let pip: PIP @@ -58,7 +58,7 @@ struct InitialPlayerSettings: Codable { let localeStrings: String? } -/// Values that the Frontend can use to determine user settings +/// Values that the frontend can use to determine user settings. public struct UserValues: Codable { enum CodingKeys: String, CodingKey { case duckPlayerMode = "privatePlayerMode" @@ -68,6 +68,7 @@ public struct UserValues: Codable { let askModeOverlayHidden: Bool } +/// UI-related values for the frontend. public struct UIValues: Codable { enum CodingKeys: String, CodingKey { case allowFirstVideo @@ -75,43 +76,138 @@ public struct UIValues: Codable { let allowFirstVideo: Bool } -public enum DuckPlayerReferrer { - case youtube, other, serp - - // Computed property to get string values - var stringValue: String { - switch self { - case .youtube: - return "youtube" - case .serp: - return "serp" - default: - return "other" - - } +// Wrapper to allow sibling properties on each event in the future. +struct TelemetryEvent: Decodable { + let attributes: Attributes +} + +// This is the first example of a new telemetry event +struct ImpressionAttributes: Decodable { + enum Layout: String, Decodable { + case landscape = "landscape-layout" + } + + let name: String + let value: Layout +} + +// Designed to represent the discriminated union used by the FE (where all events are schema-driven) +enum Attributes: Decodable { + + // more events can be added here later, without needing a new handler + case impression(ImpressionAttributes) + + private enum CodingKeys: String, CodingKey { + case name + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let name = try container.decode(String.self, forKey: .name) + + switch name { + case "impression": + let attributes = try ImpressionAttributes(from: decoder) + self = .impression(attributes) + + default: + throw DecodingError.dataCorruptedError( + forKey: .name, + in: container, + debugDescription: "Unknown name value: \(name)" + ) } + } } -protocol DuckPlayerProtocol: AnyObject { + +/// Protocol defining the Duck Player functionality. +protocol DuckPlayerControlling: AnyObject { + /// The current Duck Player settings. var settings: DuckPlayerSettings { get } + + /// The host view controller, if any. var hostView: UIViewController? { get } + /// Initializes a new instance of DuckPlayer with the provided settings and feature flagger. + /// + /// - Parameters: + /// - settings: The Duck Player settings. + /// - featureFlagger: The feature flag manager. init(settings: DuckPlayerSettings, featureFlagger: FeatureFlagger) + /// Sets user values received from the web content. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? + + /// Retrieves user values to send to the web content. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. func getUserValues(params: Any, message: WKScriptMessage) -> Encodable? + + /// Opens a video in Duck Player within the specified web view. + /// + /// - Parameters: + /// - url: The URL of the video. + /// - webView: The web view to load the video in. func openVideoInDuckPlayer(url: URL, webView: WKWebView) + + /// Opens Duck Player settings. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? + + /// Opens Duck Player information modal. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? + /// Sends a telemetry event from the FE. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + func telemetryEvent(params: Any, message: WKScriptMessage) async -> Encodable? + + /// Performs initial setup for the player. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? + + /// Performs initial setup for the overlay. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? + /// Sets the host view controller for presenting modals. + /// + /// - Parameter vc: The view controller to set as host. func setHostViewController(_ vc: UIViewController) + + /// Removes the host view controller. + func removeHostView() } -final class DuckPlayer: DuckPlayerProtocol { +/// Implementation of the DuckPlayerControlling. +final class DuckPlayer: DuckPlayerControlling { struct Constants { static let duckPlayerHost: String = "player" @@ -123,8 +219,10 @@ final class DuckPlayer: DuckPlayerProtocol { static let featureNameKey = "featureName" } + private(set) var settings: DuckPlayerSettings private(set) weak var hostView: UIViewController? + private var featureFlagger: FeatureFlagger private lazy var localeStrings: String? = { @@ -148,20 +246,37 @@ final class DuckPlayer: DuckPlayerProtocol { case overlay = "duckPlayer" } + /// Initializes a new instance of DuckPlayer with the provided settings and feature flagger. + /// + /// - Parameters: + /// - settings: The Duck Player settings. + /// - featureFlagger: The feature flag manager. init(settings: DuckPlayerSettings = DuckPlayerSettingsDefault(), featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger) { self.settings = settings self.featureFlagger = featureFlagger } - // Sets a presenting VC, so DuckPlayer can present the - // info sheet directly + /// Sets the host view controller for presenting modals. + /// + /// - Parameter vc: The view controller to set as host. public func setHostViewController(_ vc: UIViewController) { hostView = vc } + /// Removes the host view controller. + public func removeHostView() { + hostView = nil + } + // MARK: - Common Message Handlers + /// Sets user values received from the web content. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. public func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? { guard let userValues: UserValues = DecodableHelper.decode(from: params) else { assertionFailure("DuckPlayer: expected JSON representation of UserValues") @@ -169,49 +284,75 @@ final class DuckPlayer: DuckPlayerProtocol { } Task { - // Fires pixels + // Fire pixels for analytics await firePixels(message: message, userValues: userValues) - // Update Settings + // Update settings based on user values await updateSettings(userValues: userValues) } return userValues } - + + /// Updates Duck Player settings based on user values. + /// + /// - Parameter userValues: The user values to update settings with. private func updateSettings(userValues: UserValues) async { settings.setMode(userValues.duckPlayerMode) settings.setAskModeOverlayHidden(userValues.askModeOverlayHidden) } + /// Retrieves user values to send to the web content. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. public func getUserValues(params: Any, message: WKScriptMessage) -> Encodable? { - // If the user is in the 'control' group, or DP is disabled sending 'nil' effectively disables - // Duckplayer in SERP, showing old overlays. - // Fixes: https://app.asana.com/0/1207252092703676/1208450923559111 - let duckPlayerExperiment = DuckPlayerLaunchExperiment() - if featureFlagger.isFeatureOn(.duckPlayer) && duckPlayerExperiment.isEnrolled && duckPlayerExperiment.isExperimentCohort { + if featureFlagger.isFeatureOn(.duckPlayer) { return encodeUserValues() } return nil - } + /// Opens a video in Duck Player within the specified web view. + /// + /// - Parameters: + /// - url: The URL of the video. + /// - webView: The web view to load the video in. @MainActor public func openVideoInDuckPlayer(url: URL, webView: WKWebView) { webView.load(URLRequest(url: url)) } + /// Performs initial setup for the player. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. @MainActor public func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? { let webView = message.webView return await self.encodedPlayerSettings(with: webView) } + /// Performs initial setup for the overlay. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. + /// - Returns: An optional `Encodable` response. @MainActor public func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? { let webView = message.webView return await self.encodedPlayerSettings(with: webView) } + /// Opens Duck Player settings. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. public func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? { NotificationCenter.default.post( name: .settingsDeepLinkNotification, @@ -221,12 +362,33 @@ final class DuckPlayer: DuckPlayerProtocol { return nil } + /// Sends a telemetry event from the FE. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. @MainActor - public func presentDuckPlayerInfo(context: DuckPlayerModalPresenter.PresentationContext) { - guard let hostView else { return } - DuckPlayerModalPresenter(context: context).presentDuckPlayerFeatureModal(on: hostView) - } + public func telemetryEvent(params: Any, message: WKScriptMessage) async -> Encodable? { + guard let event: TelemetryEvent = DecodableHelper.decode(from: params) else { + return nil + } + + switch event.attributes { + case .impression(let attrs): + switch attrs.value { + case .landscape: + Pixel.fire(pixel: .duckPlayerLandscapeLayoutImpressions) + } + } + + return nil + } + /// Opens Duck Player information modal. + /// + /// - Parameters: + /// - params: Parameters from the web content. + /// - message: The script message containing the parameters. @MainActor public func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? { guard let body = message.body as? [String: Any], @@ -239,6 +401,18 @@ final class DuckPlayer: DuckPlayerProtocol { return nil } + /// Presents the Duck Player info modal. + /// + /// - Parameter context: The presentation context for the modal. + @MainActor + public func presentDuckPlayerInfo(context: DuckPlayerModalPresenter.PresentationContext) { + guard let hostView else { return } + DuckPlayerModalPresenter(context: context).presentDuckPlayerFeatureModal(on: hostView) + } + + /// Encodes user values for sending to the web content. + /// + /// - Returns: An instance of `UserValues`. private func encodeUserValues() -> UserValues { return UserValues( duckPlayerMode: featureFlagger.isFeatureOn(.duckPlayer) ? settings.mode : .disabled, @@ -246,12 +420,19 @@ final class DuckPlayer: DuckPlayerProtocol { ) } + /// Encodes UI values for sending to the web content. + /// + /// - Returns: An instance of `UIValues`. private func encodeUIValues() -> UIValues { UIValues( allowFirstVideo: settings.allowFirstVideo ) } + /// Prepares and encodes player settings to send to the web content. + /// + /// - Parameter webView: The web view to check for PiP capability. + /// - Returns: An instance of `InitialPlayerSettings`. @MainActor private func encodedPlayerSettings(with webView: WKWebView?) async -> InitialPlayerSettings { let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true @@ -261,16 +442,22 @@ final class DuckPlayer: DuckPlayerProtocol { let playerSettings = InitialPlayerSettings.PlayerSettings(pip: pip) let userValues = encodeUserValues() let uiValues = encodeUIValues() - let settings = InitialPlayerSettings(userValues: userValues, - ui: uiValues, - settings: playerSettings, - platform: platform, - locale: locale, - localeStrings: localeStrings) + let settings = InitialPlayerSettings( + userValues: userValues, + ui: uiValues, + settings: playerSettings, + platform: platform, + locale: locale, + localeStrings: localeStrings + ) return settings } - // Accessing WKMessage needs main thread + /// Fires analytics pixels based on user interactions. + /// + /// - Parameters: + /// - message: The script message containing the interaction data. + /// - userValues: The user values to determine which pixels to fire. @MainActor private func firePixels(message: WKScriptMessage, userValues: UserValues) { @@ -279,9 +466,35 @@ final class DuckPlayer: DuckPlayerProtocol { return } guard let feature = messageData.featureName else { return } - let event: Pixel.Event = feature == FeatureName.page.rawValue ? .duckPlayerSettingAlwaysDuckPlayer : .duckPlayerSettingAlwaysDuckPlayer - if userValues.duckPlayerMode == .enabled { - Pixel.fire(pixel: event) + + // Get the webView URL + let webView = message.webView + guard let webView = message.webView, let url = webView.url else { + return + } + + // Based on the URL, determine which pixels to fire + let isSERP = url.isDuckDuckGoSearch + + // Assume we are in the SERP Overlay + if isSERP { + switch userValues.duckPlayerMode { + case .enabled: + Pixel.fire(pixel: .duckPlayerSettingsAlwaysOverlaySERP) + case .disabled: + Pixel.fire(pixel: .duckPlayerSettingsNeverOverlaySERP) + default: break + } + + // Assume we are in the Youtube Overlay + } else { + switch userValues.duckPlayerMode { + case .enabled: + Pixel.fire(pixel: .duckPlayerSettingsAlwaysOverlayYoutube) + case .disabled: + Pixel.fire(pixel: .duckPlayerSettingsNeverOverlayYoutube) + default: break + } } } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerLaunchExperiment.swift b/DuckDuckGo/DuckPlayer/DuckPlayerLaunchExperiment.swift deleted file mode 100644 index be9821fe3e..0000000000 --- a/DuckDuckGo/DuckPlayer/DuckPlayerLaunchExperiment.swift +++ /dev/null @@ -1,239 +0,0 @@ -// -// DuckPlayerLaunchExperiment.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 - - -// Date manipulation protocol to allow testing -public protocol DuckPlayerExperimentDateProvider { - var currentDate: Date { get } -} - -public class DefaultDuckPlayerExperimentDateProvider: DuckPlayerExperimentDateProvider { - public var currentDate: Date { - return Date() - } -} - -// Wrap Pixel firing in a protocol for better testing -protocol DuckPlayerExperimentPixelFiring { - static func fireDuckPlayerExperimentPixel(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) -} - -extension Pixel: DuckPlayerExperimentPixelFiring { - static func fireDuckPlayerExperimentPixel(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) { - self.fire(pixel: pixel, withAdditionalParameters: params, onComplete: { _ in }) - } -} - - -// Experiment Protocol -protocol DuckPlayerLaunchExperimentHandling { - var isEnrolled: Bool { get } - var isExperimentCohort: Bool { get } - var duckPlayerMode: DuckPlayerMode? { get set } - func assignUserToCohort() - func fireSearchPixels() - func fireYoutubePixel(videoID: String) -} - - -final class DuckPlayerLaunchExperiment: DuckPlayerLaunchExperimentHandling { - - private struct Constants { - static let dateFormat = "yyyyMMdd" - static let enrollmentKey = "enrollment" - static let variantKey = "variant" - static let dayKey = "day" - static let weekKey = "week" - static let stateKey = "state" - static let referrerKey = "referrer" - } - - private let referrer: DuckPlayerReferrer? - var duckPlayerMode: DuckPlayerMode? - - // Abstract Pixel firing for proper testing - private let pixel: DuckPlayerExperimentPixelFiring.Type - - // Date Provider - private let dateProvider: DuckPlayerExperimentDateProvider - - @UserDefaultsWrapper(key: .duckPlayerPixelExperimentLastWeekPixelFired, defaultValue: nil) - private var lastWeekPixelFiredV2: Int? - - @UserDefaultsWrapper(key: .duckPlayerPixelExperimentLastDayPixelFired, defaultValue: nil) - private var lastDayPixelFiredV2: Int? - - @UserDefaultsWrapper(key: .duckPlayerPixelExperimentLastVideoIDRendered, defaultValue: nil) - private var lastVideoIDReportedV2: String? - - @UserDefaultsWrapper(key: .duckPlayerPixelExperimentEnrollmentDate, defaultValue: nil) - var enrollmentDateV2: Date? - - @UserDefaultsWrapper(key: .duckPlayerPixelExperimentCohort, defaultValue: nil) - var experimentCohortV2: String? - - private var isInternalUser: Bool - - enum Cohort: String { - case control - case experiment - } - - init(duckPlayerMode: DuckPlayerMode? = nil, - referrer: DuckPlayerReferrer? = nil, - userDefaults: UserDefaults = UserDefaults.standard, - pixel: DuckPlayerExperimentPixelFiring.Type = Pixel.self, - dateProvider: DuckPlayerExperimentDateProvider = DefaultDuckPlayerExperimentDateProvider(), - isInternalUser: Bool = false) { - self.referrer = referrer - self.duckPlayerMode = duckPlayerMode - self.pixel = pixel - self.dateProvider = dateProvider - self.isInternalUser = isInternalUser - } - - private var dates: (day: Int, week: Int)? { - guard isEnrolled, - let enrollmentDate = enrollmentDateV2 else { return nil } - let currentDate = dateProvider.currentDate - let calendar = Calendar.current - let dayDifference = calendar.dateComponents([.day], from: enrollmentDate, to: currentDate).day ?? 0 - let weekDifference = (dayDifference / 7) + 1 - return (day: dayDifference, week: weekDifference) - } - - private var formattedEnrollmentDate: String? { - guard isEnrolled, - let enrollmentDate = enrollmentDateV2 else { return nil } - return Self.formattedDate(enrollmentDate) - } - - static func formattedDate(_ date: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = Constants.dateFormat - dateFormatter.timeZone = TimeZone(secondsFromGMT: 0) - return dateFormatter.string(from: date) - } - - var isEnrolled: Bool { - return enrollmentDateV2 != nil && experimentCohortV2 != nil - } - - var isExperimentCohort: Bool { - return experimentCohortV2 == "experiment" - } - - func assignUserToCohort() { - if !isEnrolled { - var cohort: Cohort = Bool.random() ? .experiment : .control - - if isInternalUser { - cohort = .experiment - } - experimentCohortV2 = cohort.rawValue - enrollmentDateV2 = dateProvider.currentDate - fireEnrollmentPixel() - } - } - - private func fireEnrollmentPixel() { - guard isEnrolled, - let experimentCohortV2 = experimentCohortV2, - let formattedEnrollmentDate else { return } - - let params = [Constants.variantKey: experimentCohortV2, Constants.enrollmentKey: formattedEnrollmentDate] - pixel.fireDuckPlayerExperimentPixel(pixel: .duckplayerExperimentCohortAssign, withAdditionalParameters: params) - } - - func fireSearchPixels() { - if isEnrolled { - guard isEnrolled, - let experimentCohortV2 = experimentCohortV2, - let dates, - let formattedEnrollmentDate else { - return - } - - var params = [ - Constants.variantKey: experimentCohortV2, - Constants.dayKey: "\(dates.day)", - Constants.enrollmentKey: formattedEnrollmentDate - ] - - // Fire a base search pixel - pixel.fireDuckPlayerExperimentPixel(pixel: .duckplayerExperimentSearch, withAdditionalParameters: params) - - // Fire a daily pixel - if dates.day != lastDayPixelFiredV2 { - pixel.fireDuckPlayerExperimentPixel(pixel: .duckplayerExperimentDailySearch, withAdditionalParameters: params) - lastDayPixelFiredV2 = dates.day - } - - // Fire a weekly pixel - if dates.week != lastWeekPixelFiredV2 && dates.day > 0 { - params.removeValue(forKey: Constants.dayKey) - params[Constants.weekKey] = "\(dates.week)" - pixel.fireDuckPlayerExperimentPixel(pixel: .duckplayerExperimentWeeklySearch, withAdditionalParameters: params) - lastWeekPixelFiredV2 = dates.week - } - } - } - - func fireYoutubePixel(videoID: String) { - guard isEnrolled, - let experimentCohortV2 = experimentCohortV2, - let dates, - let formattedEnrollmentDate else { - return - } - - let params = [ - Constants.variantKey: experimentCohortV2, - Constants.dayKey: "\(dates.day)", - Constants.stateKey: duckPlayerMode?.stringValue ?? "", - Constants.referrerKey: referrer?.stringValue ?? "", - Constants.enrollmentKey: formattedEnrollmentDate - ] - if lastVideoIDReportedV2 != videoID { - pixel.fireDuckPlayerExperimentPixel(pixel: .duckplayerExperimentYoutubePageView, withAdditionalParameters: params) - lastVideoIDReportedV2 = videoID - } - } - - func cleanup() { - enrollmentDateV2 = nil - experimentCohortV2 = nil - lastDayPixelFiredV2 = nil - lastWeekPixelFiredV2 = nil - lastVideoIDReportedV2 = nil - } - - func override(control: Bool = false) { - enrollmentDateV2 = Date() - experimentCohortV2 = control ? "control" : "experiment" - lastDayPixelFiredV2 = nil - lastWeekPixelFiredV2 = nil - lastVideoIDReportedV2 = nil - - } - -} diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 16acb20fd1..adb9d58ab0 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -26,16 +26,51 @@ import BrowserServicesKit import DuckPlayer import os.log -final class DuckPlayerNavigationHandler { +/// Handles navigation and interactions related to Duck Player within the app. +final class DuckPlayerNavigationHandler: NSObject { + + /// The DuckPlayer instance used for handling video playback. + var duckPlayer: DuckPlayerControlling - var duckPlayer: DuckPlayerProtocol + /// Indicates where the DuckPlayer was referred from (e.g., YouTube, SERP). var referrer: DuckPlayerReferrer = .other - var lastHandledVideoID: String? + + /// Feature flag manager for enabling/disabling features. var featureFlagger: FeatureFlagger + + /// Application settings. var appSettings: AppSettings - var navigationType: WKNavigationType = .other - var experiment: DuckPlayerLaunchExperimentHandling - private lazy var internalUserDecider = AppDependencyProvider.shared.internalUserDecider + + /// Pixel firing utility for analytics. + var pixelFiring: PixelFiring.Type + let dailyPixelFiring: DailyPixelFiring.Type + + /// Keeps track of the last YouTube video watched. + var lastWatchInYoutubeVideo: String? + + // Redirection Throttle + /// Timestamp of the last Duck Player redirection. + private var lastDuckPlayerRedirect: Date? + + /// Duration to throttle Duck Player redirects. + private let lastDuckPlayerRedirectThrottleDuration: TimeInterval = 1 + + // Navigation URL Changing Throttle + /// Timestamp of the last URL change handling. + private var lastURLChangeHandling: Date? + + /// Duration to throttle URL change handling. + private let lastURLChangeHandlingThrottleDuration: TimeInterval = 1 + + // Navigation Cancelling Throttle + /// Timestamp of the last navigation handling. + private var lastNavigationHandling: Date? + + /// Duration to throttle navigation handling. + private let lastNavigationHandlingThrottleDuration: TimeInterval = 1 + + /// Delegate for handling tab navigation events. + weak var tabNavigationHandler: DuckPlayerTabNavigationHandling? private struct Constants { static let SERPURL = "duckduckgo.com/" @@ -50,21 +85,44 @@ final class DuckPlayerNavigationHandler { static let httpMethod = "GET" static let watchInYoutubePath = "openInYoutube" static let watchInYoutubeVideoParameter = "v" - static let urlInternalReferrer = "embeds_referring_euri" + static let youtubeEmbedURI = "embeds_referring_euri" static let youtubeScheme = "youtube://" static let duckPlayerScheme = URL.NavigationalScheme.duck.rawValue + static let duckPlayerReferrerParameter = "dp_referrer" + static let newTabParameter = "dp_isNewTab" + static let allowFirstVideoParameter = "dp_allowFirstVideo" + } + + private struct DuckPlayerParameters { + let referrer: DuckPlayerReferrer + let isNewTap: Bool + let allowFirstVideo: Bool } - init(duckPlayer: DuckPlayerProtocol = DuckPlayer(), + /// Initializes a new instance of `DuckPlayerNavigationHandler` with the provided dependencies. + /// + /// - Parameters: + /// - duckPlayer: The DuckPlayer instance. + /// - featureFlagger: The feature flag manager. + /// - appSettings: The application settings. + /// - pixelFiring: The pixel firing utility for analytics. + /// - dailyPixelFiring: The daily pixel firing utility for analytics. + /// - tabNavigationHandler: The tab navigation handler delegate. + init(duckPlayer: DuckPlayerControlling = DuckPlayer(), featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, appSettings: AppSettings, - experiment: DuckPlayerLaunchExperimentHandling = DuckPlayerLaunchExperiment()) { + pixelFiring: PixelFiring.Type = Pixel.self, + dailyPixelFiring: DailyPixelFiring.Type = DailyPixel.self, + tabNavigationHandler: DuckPlayerTabNavigationHandling? = nil) { self.duckPlayer = duckPlayer self.featureFlagger = featureFlagger self.appSettings = appSettings - self.experiment = experiment + self.pixelFiring = pixelFiring + self.dailyPixelFiring = dailyPixelFiring + self.tabNavigationHandler = tabNavigationHandler } + /// Returns the file path for the Duck Player HTML template. static var htmlTemplatePath: String { guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, ofType: Constants.templateExtension, @@ -75,6 +133,10 @@ final class DuckPlayerNavigationHandler { return file } + /// Creates a `URLRequest` for Duck Player using the original request's YouTube video ID and timestamp. + /// + /// - Parameter originalRequest: The original YouTube `URLRequest`. + /// - Returns: A new `URLRequest` pointing to the Duck Player. static func makeDuckPlayerRequest(from originalRequest: URLRequest) -> URLRequest { guard let (youtubeVideoID, timestamp) = originalRequest.url?.youtubeVideoParams else { assertionFailure("Request should have ID") @@ -83,6 +145,12 @@ final class DuckPlayerNavigationHandler { return makeDuckPlayerRequest(for: youtubeVideoID, timestamp: timestamp) } + /// Generates a `URLRequest` for Duck Player with a specific YouTube video ID and optional timestamp. + /// + /// - Parameters: + /// - videoID: The YouTube video ID. + /// - timestamp: Optional timestamp for the video. + /// - Returns: A `URLRequest` configured for Duck Player. static func makeDuckPlayerRequest(for videoID: String, timestamp: String?) -> URLRequest { var request = URLRequest(url: .youtubeNoCookie(videoID, timestamp: timestamp)) request.addValue(Constants.localhost, forHTTPHeaderField: Constants.refererHeader) @@ -90,6 +158,9 @@ final class DuckPlayerNavigationHandler { return request } + /// Loads and returns the HTML content from the Duck Player template file. + /// + /// - Returns: The HTML content as a `String`. static func makeHTMLFromTemplate() -> String { guard let html = try? String(contentsOfFile: htmlTemplatePath) else { assertionFailure("Should be able to load template") @@ -98,72 +169,57 @@ final class DuckPlayerNavigationHandler { return html } + /// Navigates to the Duck Player URL in the web view. Opens in a new tab if settings dictate. + /// + /// - Parameters: + /// - request: The `URLRequest` to navigate to. + /// - responseHTML: The HTML content to load. + /// - webView: The `WKWebView` to load the content into. + @MainActor private func performNavigation(_ request: URLRequest, responseHTML: String, webView: WKWebView) { - webView.loadSimulatedRequest(request, responseHTML: responseHTML) + + // If DuckPlayer is enabled, and we're watching a video in YouTube (temporarily) + // Any direct navigation to a duck:// URL should open in a new tab + if let url = webView.url, url.isYoutubeWatch && isOpenInNewTabEnabled && duckPlayerMode == .enabled { + self.redirectToDuckPlayerVideo(url: request.url, webView: webView) + return + } + // Otherwise, just load the simulated request + // New tabs require a short interval so the Omnibars dismissal propagates + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + webView.loadSimulatedRequest(request, responseHTML: responseHTML) + } } + /// Handles the Duck Player request by generating HTML from the template and performing navigation. + /// + /// - Parameters: + /// - request: The `URLRequest` to handle. + /// - webView: The `WKWebView` to load the content into. + @MainActor private func performRequest(request: URLRequest, webView: WKWebView) { let html = Self.makeHTMLFromTemplate() let duckPlayerRequest = Self.makeDuckPlayerRequest(from: request) performNavigation(duckPlayerRequest, responseHTML: html, webView: webView) } - private var duckPlayerMode: DuckPlayerMode { - let isEnabled = experiment.isEnrolled && experiment.isExperimentCohort && featureFlagger.isFeatureOn(.duckPlayer) - return isEnabled ? duckPlayer.settings.mode : .disabled + /// Checks if the Duck Player feature is enabled via feature flags. + private var isDuckPlayerFeatureEnabled: Bool { + featureFlagger.isFeatureOn(.duckPlayer) } - // Handle URL changes not triggered via Omnibar - // such as changes triggered via JS - @MainActor - private func handleURLChange(url: URL?, webView: WKWebView) { - - guard let url else { return } - - guard featureFlagger.isFeatureOn(.duckPlayer) else { - return - } - - // This is passed to the FE overlay at init to disable the overlay for one video - duckPlayer.settings.allowFirstVideo = false - - if let (videoID, _) = url.youtubeVideoParams, - videoID == lastHandledVideoID { - Logger.duckPlayer.debug("URL (\(url.absoluteString) already handled, skipping") - return - } - - // Handle Youtube internal links like "Age restricted" and "Copyright restricted" videos - // These should not be handled by DuckPlayer - if url.isYoutubeVideo, - url.hasWatchInYoutubeQueryParameter { - duckPlayer.settings.allowFirstVideo = true - return - } - - if url.isYoutubeVideo, - !url.isDuckPlayer, - let (videoID, timestamp) = url.youtubeVideoParams, - duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk { - - Logger.duckPlayer.debug("Handling URL change: \(url.absoluteString)") - webView.load(URLRequest(url: URL.duckPlayer(videoID, timestamp: timestamp))) - lastHandledVideoID = videoID - } + /// Determines if "Open in New Tab" for Duck Player is enabled in the settings. + private var isOpenInNewTabEnabled: Bool { + featureFlagger.isFeatureOn(.duckPlayer) && featureFlagger.isFeatureOn(.duckPlayerOpenInNewTab) && duckPlayer.settings.openInNewTab && duckPlayerMode != .disabled } - // Get the duck:// URL youtube-no-cookie URL - func getDuckURLFor(_ url: URL) -> URL { - guard let (youtubeVideoID, timestamp) = url.youtubeVideoParams, - url.isDuckPlayer, - !url.isDuckURLScheme, - duckPlayerMode != .disabled - else { - return url - } - return URL.duckPlayer(youtubeVideoID, timestamp: timestamp) + /// Retrieves the current mode of Duck Player based on feature flags and user settings. + private var duckPlayerMode: DuckPlayerMode { + let isEnabled = isDuckPlayerFeatureEnabled + return isEnabled ? duckPlayer.settings.mode : .disabled } + /// Checks if the YouTube app is installed on the device. private var isYouTubeAppInstalled: Bool { if let youtubeURL = URL(string: Constants.youtubeScheme) { return UIApplication.shared.canOpenURL(youtubeURL) @@ -171,31 +227,24 @@ final class DuckPlayerNavigationHandler { return false } - private func isSERPLink(navigationAction: WKNavigationAction) -> Bool { - guard let referrer = navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] else { - return false - } - if referrer.contains(Constants.SERPURL) { - return true - } - return false - } - - private func isOpenInYoutubeURL(url: URL) -> Bool { - return isWatchInYouTubeURL(url: url) - } - + /// Extracts a YouTube URL from a Duck Player "Open in YouTube" link. + /// + /// - Parameter url: The URL to parse. + /// - Returns: A YouTube `URL` if available. private func getYoutubeURLFromOpenInYoutubeLink(url: URL) -> URL? { guard isWatchInYouTubeURL(url: url), let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), let videoParameterItem = urlComponents.queryItems?.first(where: { $0.name == Constants.watchInYoutubeVideoParameter }), - let id = videoParameterItem.value, - let newURL = URL.youtube(id, timestamp: nil).addingWatchInYoutubeQueryParameter() else { + let id = videoParameterItem.value else { return nil } - return newURL + return URL.youtube(id, timestamp: nil) } + /// Determines if the URL is an "Open in YouTube" Duck Player link. + /// + /// - Parameter url: The URL to check. + /// - Returns: `true` if it's an "Open in YouTube" link, `false` otherwise. private func isWatchInYouTubeURL(url: URL) -> Bool { guard url.scheme == Constants.duckPlayerScheme, let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), @@ -205,368 +254,678 @@ final class DuckPlayerNavigationHandler { return true } - // DuckPlayer Experiment Handling - private func handleYouTubePageVisited(url: URL?, navigationAction: WKNavigationAction?) { - guard let url else { return } + /// Redirects the web view to play the video in Duck Player, optionally forcing a new tab. + /// + /// - Parameters: + /// - url: The URL of the video. + /// - webView: The `WKWebView` to load the content into. + /// - forceNewTab: Whether to force opening in a new tab. + /// - disableNewTab: Ignore openInNewTab settings + @MainActor + private func redirectToDuckPlayerVideo(url: URL?, webView: WKWebView, forceNewTab: Bool = false, disableNewTab: Bool = false) { - // Parse openInYoutubeURL if present - let newURL = getYoutubeURLFromOpenInYoutubeLink(url: url) ?? url + guard let url, + let (videoID, _) = url.youtubeVideoParams else { return } - guard let (videoID, _) = newURL.youtubeVideoParams else { return } + let duckPlayerURL = URL.duckPlayer(videoID) + self.loadWithDuckPlayerParameters(URLRequest(url: duckPlayerURL), referrer: self.referrer, webView: webView, forceNewTab: forceNewTab, disableNewTab: disableNewTab) + } + + /// Redirects to the YouTube video page, allowing the first video if necessary. + /// + /// - Parameters: + /// - url: The URL of the video. + /// - webView: The `WKWebView` to load the content into. + /// - forceNewTab: Whether to force opening in a new tab. + /// - allowFirstVideo: Hide DuckPlayer Overlay in the first loaded video + /// - disableNewTab: Ignore openInNewTab settings + @MainActor + private func redirectToYouTubeVideo(url: URL?, webView: WKWebView, forceNewTab: Bool = false, allowFirstVideo: Bool = true, disableNewTab: Bool = false) { - // If this is a SERP link, set the referrer accordingly - if let navigationAction, isSERPLink(navigationAction: navigationAction) { - referrer = .serp + guard let url else { return } + + var redirectURL = url + + // Parse OpenInYouTubeURLs if present + if let parsedURL = getYoutubeURLFromOpenInYoutubeLink(url: url) { + redirectURL = parsedURL } + + // When redirecting to YouTube, we always allow the first video + loadWithDuckPlayerParameters(URLRequest(url: redirectURL), referrer: referrer, webView: webView, forceNewTab: forceNewTab, allowFirstVideo: allowFirstVideo, disableNewTab: disableNewTab) + } + + + /// Fires analytics pixels when Duck Player is viewed, based on referrer and settings. + private func fireDuckPlayerPixels(webView: WKWebView) { - if featureFlagger.isFeatureOn(.duckPlayer) || internalUserDecider.isInternalUser { - - // DuckPlayer Experiment run - let experiment = DuckPlayerLaunchExperiment(duckPlayerMode: duckPlayerMode, - referrer: referrer, - isInternalUser: internalUserDecider.isInternalUser) - - // Enroll user if not enrolled - if !experiment.isEnrolled { - experiment.assignUserToCohort() - - // DuckPlayer is disabled before user enrolls, - // So trigger a settings change notification - // to let the FE know about the 'actual' setting - // and update Experiment value - if experiment.isExperimentCohort { - duckPlayer.settings.triggerNotification() - experiment.duckPlayerMode = duckPlayer.settings.mode + // First daily unique user Duck Player view + dailyPixelFiring.fireDaily(.duckPlayerDailyUniqueView, withAdditionalParameters: ["settings": duckPlayerMode.stringValue]) + + // Duck Player viewed with Always setting, referred from YouTube (automatic) + if (referrer == .youtube) && duckPlayerMode == .enabled { + pixelFiring.fire(.duckPlayerViewFromYoutubeAutomatic, withAdditionalParameters: [:]) + } + + // Duck Player viewed from SERP + if referrer == .serp { + pixelFiring.fire(.duckPlayerViewFromSERP, withAdditionalParameters: [:]) + } + + // Other referrers + if referrer == .other || referrer == .undefined { + pixelFiring.fire(.duckPlayerViewFromOther, withAdditionalParameters: [:]) + } + + } + + /// Fires an analytics pixel when the user opts to watch a video on YouTube instead. + private func fireOpenInYoutubePixel() { + pixelFiring.fire(.duckPlayerWatchOnYoutube, withAdditionalParameters: [:]) + } + + /// Cancels JavaScript-triggered navigation by stopping the load and going back if possible. + /// + /// - Parameters: + /// - webView: The `WKWebView` to manipulate. + /// - completion: Optional completion handler. + @MainActor + private func cancelJavascriptNavigation(webView: WKWebView, completion: (() -> Void)? = nil) { + + if duckPlayerMode == .enabled { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + webView.stopLoading() + if webView.canGoBack { + webView.goBack() } + completion?() + } + } else { + completion?() + } + + } + + /// Loads a request with Duck Player parameters, handling new tab logic and first video allowance. + /// + /// - Parameters: + /// - request: The `URLRequest` to load. + /// - referrer: The referrer information. + /// - webView: The `WKWebView` to load the content into. + /// - forceNewTab: Whether to force opening in hana new tab. + /// - allowFirstVideo: Whether to allow the first video to play. + /// - disableNewTab: Ignores Open in New tab settings + private func loadWithDuckPlayerParameters(_ request: URLRequest, + referrer: DuckPlayerReferrer, + webView: WKWebView, + forceNewTab: Bool = false, + allowFirstVideo: Bool = false, + disableNewTab: Bool = false) { + + guard let url = request.url else { + return + } + + // We want to prevent multiple simultaneous redirects + // This can be caused by Duplicate Nav events, and YouTube's own redirects + if let lastTimestamp = lastDuckPlayerRedirect { + let timeSinceLastThrottle = Date().timeIntervalSince(lastTimestamp) + if timeSinceLastThrottle < lastDuckPlayerRedirectThrottleDuration { + return } + } + lastDuckPlayerRedirect = Date() + + // Remove any DP Parameters + guard let strippedURL = removeDuckPlayerParameters(from: url) else { + return + } + + // Set allowFirstVideo + duckPlayer.settings.allowFirstVideo = allowFirstVideo + + // Get parameter values + let isNewTab = (isOpenInNewTabEnabled && duckPlayerMode == .enabled) || forceNewTab ? "1" : "0" + let allowFirstVideo = allowFirstVideo ? "1" : "0" + let referrer = referrer.rawValue + + var newURL = strippedURL + var urlComponents = URLComponents(url: strippedURL, resolvingAgainstBaseURL: false) + var queryItems = urlComponents?.queryItems ?? [] - experiment.fireYoutubePixel(videoID: videoID) + // Append DuckPlayer parameters + queryItems.append(URLQueryItem(name: Constants.newTabParameter, value: isNewTab)) + queryItems.append(URLQueryItem(name: Constants.duckPlayerReferrerParameter, value: referrer)) + queryItems.append(URLQueryItem(name: Constants.allowFirstVideoParameter, value: allowFirstVideo)) + urlComponents?.queryItems = queryItems + + // Create a new request with the modified URL + newURL = urlComponents?.url ?? newURL + + // Only Open in new tab if enabled + if (isOpenInNewTabEnabled || forceNewTab) && !disableNewTab { + tabNavigationHandler?.openTab(for: newURL) + } else { + webView.load(URLRequest(url: newURL)) } - + + Logger.duckPlayer.debug("DP: loadWithDuckPlayerParameters: \(newURL.absoluteString)") + + } + + /// Extracts Duck Player-specific parameters from the URL for internal use. + /// + /// - Parameter url: The URL to parse. + /// - Returns: A `DuckPlayerParameters` struct containing the extracted values. + private func getDuckPlayerParameters(url: URL) -> DuckPlayerParameters { + + guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = urlComponents.queryItems else { + return DuckPlayerParameters(referrer: .other, isNewTap: false, allowFirstVideo: false) + } + + let referrerValue = queryItems.first(where: { $0.name == Constants.duckPlayerReferrerParameter })?.value + let allowFirstVideoValue = queryItems.first(where: { $0.name == Constants.allowFirstVideoParameter })?.value + let isNewTabValue = queryItems.first(where: { $0.name == Constants.newTabParameter })?.value + let youtubeEmbedURI = queryItems.first(where: { $0.name == Constants.youtubeEmbedURI })?.value + + // Use the from(string:) method to parse referrer + let referrer = DuckPlayerReferrer(string: referrerValue ?? "") + let allowFirstVideo = allowFirstVideoValue == "1" || youtubeEmbedURI.map(\.isEmpty) ?? false + let isNewTab = isNewTabValue == "1" + + return DuckPlayerParameters(referrer: referrer, isNewTap: isNewTab, allowFirstVideo: allowFirstVideo) + } + + /// Removes Duck Player-specific query parameters from a URL. + /// + /// - Parameter url: The URL to clean. + /// - Returns: A new URL without Duck Player parameters. + private func removeDuckPlayerParameters(from url: URL) -> URL? { + guard var components = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = components.queryItems else { + return url + } + + let parametersToRemove = [Constants.newTabParameter, + Constants.duckPlayerReferrerParameter] + + // Filter out the parameters you want to remove + components.queryItems = queryItems.filter { !parametersToRemove.contains($0.name) } + + // Return the modified URL + return components.url + } + + /// Determines if a URL is a DuckPlayer redirect based on its parameters + /// + /// - Parameter url: To check + /// - Returns: True | False + private func isDuckPlayerRedirect(url: URL) -> Bool { + + guard let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), + let queryItems = urlComponents.queryItems else { + return false + } + + let referrerValue = queryItems.first(where: { $0.name == Constants.duckPlayerReferrerParameter })?.value + let allowFirstVideoValue = queryItems.first(where: { $0.name == Constants.allowFirstVideoParameter })?.value + let isNewTabValue = queryItems.first(where: { $0.name == Constants.newTabParameter })?.value + let youtubeEmbedURI = queryItems.first(where: { $0.name == Constants.youtubeEmbedURI })?.value + + return referrerValue != nil || allowFirstVideoValue != nil || isNewTabValue != nil || youtubeEmbedURI != nil + } + + /// Sets the referrer based on the current web view URL to aid in analytics. + /// + /// - Parameter webView: The `WKWebView` whose URL is used to determine the referrer. + private func setReferrer(webView: WKWebView) { + + // Make sure we are NOT DuckPlayer + guard let url = webView.url, !url.isDuckPlayer else { return } + + // First, try to use the back Item + var backItems = webView.backForwardList.backList.reversed() + + // Ignore any previous URL that's duckPlayer or youtube-no-cookie + if backItems.first?.url != nil, url.isDuckPlayer { + backItems = webView.backForwardList.backList.dropLast().reversed() + } + + // If the current URL is DuckPlayer, use the previous history item + guard let referrerURL = url.isDuckPlayer ? backItems.first?.url : url else { + return + } + + // SERP as a referrer + if referrerURL.isDuckDuckGoSearch { + referrer = .serp + return + } + + // Set to Youtube for "Watch in Youtube videos" + if referrerURL.isYoutubeWatch && duckPlayerMode == .enabled && duckPlayer.settings.allowFirstVideo { + referrer = .youtube + return + } + + // Set to Overlay for Always ask + if referrerURL.isYoutubeWatch && duckPlayerMode == .alwaysAsk { + referrer = .youtubeOverlay + return + } + + // Any Other Youtube URL or other referrer + if referrerURL.isYoutube { + referrer = .youtube + return + } else { + referrer = .other + } + } - // Determines if the link should be opened in a new tab - // And sets the correct navigationType - // This is uses for JS based navigation links - private func setOpenInNewTab(url: URL?) { - guard let url else { + /// Determines if the current tab is a new tab based on the targetFrame request and other params + /// + /// - Parameter navigationAction: The `WKNavigationAction` used to determine the tab type. + private func isNewTab(_ navigationAction: WKNavigationAction) -> Bool { + + guard let request = navigationAction.targetFrame?.safeRequest, + let url = request.url else { + return false + } + + // Always return false if open in new tab is disabled + guard isOpenInNewTabEnabled else { return false } + + // If the target frame is duckPlayer itself or there's no URL + // we're at a new tab + if url.isDuckPlayer || url.isEmpty { + return true + } + + return false + } + + /// // Handle "open in YouTube" links (duck://player/openInYoutube) + /// + /// - Parameter url: The `URL` used to determine the tab type. + /// - Parameter webView: The `WebView` used for navigation/redirection + @MainActor + private func handleOpenInYoutubeLink(url: URL, webView: WKWebView) { + + // Handle "open in YouTube" links (duck://player/openInYoutube) + guard let (videoID, _) = url.youtubeVideoParams else { return } - // let openInNewTab = appSettings.duckPlayerOpenInNewTab - let openInNewTab = appSettings.duckPlayerOpenInNewTab - let isFeatureEnabled = featureFlagger.isFeatureOn(.duckPlayer) - let isSubFeatureEnabled = featureFlagger.isFeatureOn(.duckPlayerOpenInNewTab) || internalUserDecider.isInternalUser - let isDuckPlayerEnabled = duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk + // Fire a Pixel for Open in YouTube + self.fireOpenInYoutubePixel() - if openInNewTab && - isFeatureEnabled && - isSubFeatureEnabled && - isDuckPlayerEnabled { - navigationType = .linkActivated + // Attempt to open in YouTube app or load in webView + if appSettings.allowUniversalLinks, isYouTubeAppInstalled, + let youtubeAppURL = URL(string: "\(Constants.youtubeScheme)\(videoID)") { + UIApplication.shared.open(youtubeAppURL) } else { - navigationType = .other + // Watch in YT videos always open in new tab + redirectToYouTubeVideo(url: url, webView: webView, forceNewTab: true) } + + } } extension DuckPlayerNavigationHandler: DuckPlayerNavigationHandling { - - // Handle rendering the simulated request if the URL is duck:// - // and DuckPlayer is either enabled or alwaysAsk + + /// Manages navigation actions to Duck Player URLs, handling redirects and loading as needed. + /// + /// - Parameters: + /// - navigationAction: The `WKNavigationAction` to handle. + /// - webView: The `WKWebView` where navigation is occurring. @MainActor - func handleNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView) { + func handleDuckNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView) { - Logger.duckPlayer.debug("Handling DuckPlayer Player Navigation for \(navigationAction.request.url?.absoluteString ?? "")") - - // This is passed to the FE overlay at init to disable the overlay for one video - duckPlayer.settings.allowFirstVideo = false - - guard let url = navigationAction.request.url else { return } - - guard featureFlagger.isFeatureOn(.duckPlayer) else { return } - - // Handle Youtube internal links like "Age restricted" and "Copyright restricted" videos - // These should not be handled by DuckPlayer - if url.isYoutubeVideo, - url.hasWatchInYoutubeQueryParameter { - return + // We want to prevent multiple simultaneous redirects + // This can be caused by Duplicate Nav events, and quick URL changes + if let lastTimestamp = lastNavigationHandling, + Date().timeIntervalSince(lastTimestamp) < lastNavigationHandlingThrottleDuration { + return } - // Handle Open in Youtube Links - // duck://player/openInYoutube?v=12345 - if let newURL = getYoutubeURLFromOpenInYoutubeLink(url: url) { - - Pixel.fire(pixel: Pixel.Event.duckPlayerWatchOnYoutube) - - // These links should always skip the overlay - duckPlayer.settings.allowFirstVideo = true + lastNavigationHandling = Date() - // Attempt to open in YouTube app (if installed) or load in webView - if appSettings.allowUniversalLinks, - isYouTubeAppInstalled, - let (videoID, _) = newURL.youtubeVideoParams, - let url = URL(string: "\(Constants.youtubeScheme)\(videoID)") { - UIApplication.shared.open(url) - } else { - webView.load(URLRequest(url: newURL)) + guard let url = navigationAction.request.url else { return } + + // Redirect to YouTube if DuckPlayer is disabled + guard duckPlayerMode != .disabled else { + if let (videoID, _) = url.youtubeVideoParams { + redirectToYouTubeVideo(url: URL.youtube(videoID), webView: webView) } return } - - // Daily Unique View Pixel - if url.isDuckPlayer, - duckPlayerMode != .disabled { - let setting = duckPlayerMode == .enabled ? Constants.duckPlayerAlwaysString : Constants.duckPlayerDefaultString - DailyPixel.fire(pixel: Pixel.Event.duckPlayerDailyUniqueView, withAdditionalParameters: [Constants.settingsKey: setting]) + // Handle "open in YouTube" links (duck://player/openInYoutube) + if let openInYouTubeURL = getYoutubeURLFromOpenInYoutubeLink(url: url) { + handleOpenInYoutubeLink(url: openInYouTubeURL, webView: webView) + return } - // Pixel for Views From Youtube - if referrer == .youtube, - duckPlayerMode == .enabled { - Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeAutomatic) - } - + // Determine navigation type + let shouldOpenInNewTab = isOpenInNewTabEnabled && !isNewTab(navigationAction) + + // Handle duck:// scheme URLs (Or direct navigation to duck player) if url.isDuckURLScheme { - - // If DuckPlayer is Enabled or in ask mode, render the video - if duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk, + + // If should be opened in a new tab, and it's not a DuckPlayer URL, it means this + // is a direct duck:// navigation, so we need to properly redirect to a duckPlayer version + if shouldOpenInNewTab && !isDuckPlayerRedirect(url: url) { + redirectToDuckPlayerVideo(url: url, webView: webView, forceNewTab: true) + return + } + + // Simulate DuckPlayer request if in enabled/ask mode and not redirected to YouTube + if duckPlayerMode != .disabled, !url.hasWatchInYoutubeQueryParameter { let newRequest = Self.makeDuckPlayerRequest(from: URLRequest(url: url)) - Logger.duckPlayer.debug("DP: Loading Simulated Request for \(navigationAction.request.url?.absoluteString ?? "")") - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + // The webView needs some time for state to propagate + // Before performing the simulated request + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { self.performRequest(request: newRequest, webView: webView) + self.fireDuckPlayerPixels(webView: webView) } - - // Otherwise, just redirect to YouTube } else { - if let (videoID, timestamp) = url.youtubeVideoParams { - let youtubeURL = URL.youtube(videoID, timestamp: timestamp) - let request = URLRequest(url: youtubeURL) - webView.load(request) - } + redirectToYouTubeVideo(url: url, webView: webView) } return } - + + // Handle YouTube watch URLs based on DuckPlayer settings + if url.isYoutubeWatch, duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk { + if url.hasWatchInYoutubeQueryParameter { + redirectToYouTubeVideo(url: url, webView: webView) + } else { + redirectToDuckPlayerVideo(url: url, webView: webView, forceNewTab: shouldOpenInNewTab) + } + } } - // DecidePolicyFor handler to redirect relevant requests - // to duck://player + /// Observes URL changes and redirects to Duck Player when appropriate, avoiding duplicate handling. + /// + /// - Parameter webView: The `WKWebView` whose URL has changed. + /// - Returns: A result indicating whether the URL change was handled. @MainActor - func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, - completion: @escaping (WKNavigationActionPolicy) -> Void, - webView: WKWebView) { + func handleURLChange(webView: WKWebView) -> DuckPlayerNavigationHandlerURLChangeResult { - Logger.duckPlayer.debug("Handling DecidePolicyFor for \(navigationAction.request.url?.absoluteString ?? "")") - - // This means navigation originated in user Event - // and not automatic. This is used further to - // determine how navigation is performed (new tab, etc) - // Resets on next attachment - if navigationAction.navigationType == .linkActivated { - self.navigationType = navigationAction.navigationType + // We want to prevent multiple simultaneous redirects + // This can be caused by Duplicate Nav events, and quick URL changes + if let lastTimestamp = lastURLChangeHandling, + Date().timeIntervalSince(lastTimestamp) < lastURLChangeHandlingThrottleDuration { + return .notHandled(.duplicateNavigation) } - guard let url = navigationAction.request.url else { - completion(.cancel) - return - } + // Update the Referrer based on the first URL change detected + setReferrer(webView: webView) - guard featureFlagger.isFeatureOn(.duckPlayer) else { - completion(.allow) - return + // We don't want YouTube redirects happening while default navigation is happening + // This can be caused by Duplicate Nav events, and quick URL changes + if let lastTimestamp = lastNavigationHandling, + Date().timeIntervalSince(lastTimestamp) < lastNavigationHandlingThrottleDuration { + return .notHandled(.duplicateNavigation) } - // This is passed to the FE overlay at init to disable the overlay for one video - duckPlayer.settings.allowFirstVideo = false - - if let (videoID, _) = url.youtubeVideoParams, - videoID == lastHandledVideoID, - !url.hasWatchInYoutubeQueryParameter { - Logger.duckPlayer.debug("DP: DecidePolicy: URL (\(url.absoluteString)) already handled, skipping") - completion(.cancel) - return + // Check if DuckPlayer feature is enabled + guard isDuckPlayerFeatureEnabled else { + return .notHandled(.featureOff) } - // Handle Youtube internal links like "Age restricted" and "Copyright restricted" videos - // These should not be handled by DuckPlayer and not include overlays - if url.isYoutubeVideo, - url.hasWatchInYoutubeQueryParameter { - duckPlayer.settings.allowFirstVideo = true - completion(.allow) - return - } - - // SERP referals - if isSERPLink(navigationAction: navigationAction) { - // Set the referer - referrer = .serp - - if duckPlayerMode == .enabled, !url.isDuckPlayer { - Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromSERP, debounce: 2) - } - - } else { - Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromOther, debounce: 2) + guard let url = webView.url, let (videoID, _) = url.youtubeVideoParams else { + return .notHandled(.invalidURL) } - - if url.isYoutubeVideo, - !url.isDuckPlayer, - duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk { - Logger.duckPlayer.debug("DP: Handling decidePolicy for Duck Player with \(url.absoluteString)") - completion(.cancel) - handleURLChange(url: url, webView: webView) - return + guard url.isYoutubeWatch else { + return .notHandled(.isNotYoutubeWatch) } - completion(.allow) - } - - @MainActor - func handleJSNavigation(url: URL?, webView: WKWebView) { + guard videoID != lastWatchInYoutubeVideo else { + lastURLChangeHandling = Date() + return .handled + } - Logger.duckPlayer.debug("Handling JS Navigation for \(url?.absoluteString ?? "")") + let parameters = getDuckPlayerParameters(url: url) - guard featureFlagger.isFeatureOn(.duckPlayer) else { - return + // If the URL has the allow first video, we just don't handle it + if parameters.allowFirstVideo { + lastWatchInYoutubeVideo = videoID + lastURLChangeHandling = Date() + return .handled } - // Assume JS Navigation is user-triggered - self.navigationType = .linkActivated - - // Only handle URL changes if the allowFirstVideo is set to false - // This prevents Youtube redirects from triggering DuckPlayer when is not expected - if !duckPlayer.settings.allowFirstVideo { - handleURLChange(url: url, webView: webView) + guard duckPlayerMode == .enabled else { + return .notHandled(.duckPlayerDisabled) } + + // Handle YouTube watch URLs based on DuckPlayer settings + if duckPlayerMode == .enabled && !parameters.allowFirstVideo { + cancelJavascriptNavigation(webView: webView, completion: { + self.redirectToDuckPlayerVideo(url: url, webView: webView) + }) + lastURLChangeHandling = Date() + Logger.duckPlayer.debug("Handling URL change for \(webView.url?.absoluteString ?? "")") + return .handled + } else { + + } + + return .notHandled(.isNotYoutubeWatch) } + /// Custom back navigation logic to handle Duck Player in the web view's history stack. + /// + /// - Parameter webView: The `WKWebView` to navigate back in. @MainActor func handleGoBack(webView: WKWebView) { - - Logger.duckPlayer.debug("DP: Handling Back Navigation") - - let experiment = DuckPlayerLaunchExperiment() - let duckPlayerMode = experiment.isExperimentCohort ? duckPlayerMode : .disabled - - guard featureFlagger.isFeatureOn(.duckPlayer) else { + + guard isDuckPlayerFeatureEnabled else { webView.goBack() return } - lastHandledVideoID = nil - webView.stopLoading() - - // Check if the back list has items + // Check if the back list has items, and if not try to close the tab guard !webView.backForwardList.backList.isEmpty else { - webView.goBack() + tabNavigationHandler?.closeTab() return } - + // Find the last non-YouTube video URL in the back list - // and navigate to it let backList = webView.backForwardList.backList var nonYoutubeItem: WKBackForwardListItem? - + for item in backList.reversed() where !item.url.isYoutubeVideo && !item.url.isDuckPlayer { nonYoutubeItem = item break } - + if let nonYoutubeItem = nonYoutubeItem, duckPlayerMode == .enabled { - Logger.duckPlayer.debug("DP: Navigating back to \(nonYoutubeItem.url.absoluteString)") + // Delay stopping the loading to avoid interference with go(to:) + webView.stopLoading() webView.go(to: nonYoutubeItem) } else { - Logger.duckPlayer.debug("DP: Navigating back to previous page") + webView.stopLoading() webView.goBack() } } + - // Handle Reload for DuckPlayer Videos + /// Handles reload actions, ensuring Duck Player settings are respected during the reload. + /// + /// - Parameter webView: The `WKWebView` to reload. @MainActor func handleReload(webView: WKWebView) { - Logger.duckPlayer.debug("DP: Handling Reload") - - guard featureFlagger.isFeatureOn(.duckPlayer) else { + // Reset DuckPlayer status + duckPlayer.settings.allowFirstVideo = false + + guard isDuckPlayerFeatureEnabled else { webView.reload() return } - lastHandledVideoID = nil - webView.stopLoading() - if let url = webView.url, url.isDuckPlayer, - !url.isDuckURLScheme, - let (videoID, timestamp) = url.youtubeVideoParams, - duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk { - Logger.duckPlayer.debug("DP: Handling DuckPlayer Reload for \(url.absoluteString)") - webView.load(URLRequest(url: .duckPlayer(videoID, timestamp: timestamp))) - } else { - webView.reload() + guard let url = webView.url else { + return + } + + if url.isDuckPlayer, duckPlayerMode != .disabled { + redirectToDuckPlayerVideo(url: url, webView: webView, disableNewTab: true) + return + } + + if url.isYoutubeWatch, duckPlayerMode == .alwaysAsk { + redirectToYouTubeVideo(url: url, webView: webView, allowFirstVideo: false, disableNewTab: true) + return } + + webView.reload() + } + /// Initializes settings and potentially redirects when the handler is attached to a web view. + /// + /// - Parameter webView: The `WKWebView` being attached. @MainActor func handleAttach(webView: WKWebView) { - Logger.duckPlayer.debug("DP: Attach WebView") + // Reset referrer and initial settings + referrer = .other - guard featureFlagger.isFeatureOn(.duckPlayer) else { + // Ensure feature and mode are enabled + guard isDuckPlayerFeatureEnabled, + let url = webView.url, + duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk else { return } - if let url = webView.url, url.isDuckPlayer, - !url.isDuckURLScheme, - duckPlayerMode == .enabled || duckPlayerMode == .alwaysAsk { - Logger.duckPlayer.debug("DP: Handling Initial Load of a video for \(url.absoluteString)") - handleReload(webView: webView) + // Get parameters and determine redirection + let parameters = getDuckPlayerParameters(url: url) + if parameters.allowFirstVideo { + redirectToYouTubeVideo(url: url, webView: webView) + } else { + referrer = parameters.referrer + redirectToDuckPlayerVideo(url: url, webView: webView, disableNewTab: true) } + } + + /// Updates the referrer after the web view finishes loading a page. + /// + /// - Parameter webView: The `WKWebView` that finished loading. + @MainActor + func handleDidFinishLoading(webView: WKWebView) { + + // Reset allowFirstVideo + duckPlayer.settings.allowFirstVideo = false } - // Handle custom events - // This method is used to delegate tasks to DuckPlayerHandler, such as firing pixels and etc. - func handleEvent(event: DuckPlayerNavigationEvent, url: URL?, navigationAction: WKNavigationAction?) { - switch event { - case .youtubeVideoPageVisited: - handleYouTubePageVisited(url: url, navigationAction: navigationAction) - case .JSTriggeredNavigation: - setOpenInNewTab(url: url) + /// Resets settings when the web view starts loading a new page. + /// + /// - Parameter webView: The `WKWebView` that started loading. + @MainActor + func handleDidStartLoading(webView: WKWebView) { + + setReferrer(webView: webView) + + // Automatically reset allowFirstVideo after loading starts + // This is a fallback as the WKNavigation Delegate does not + // Always fires finishLoading (For JS Navigation) which + // triggers handleDidFinishLoading + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { + self.duckPlayer.settings.allowFirstVideo = false } + } - // Determine if the links should be open in a new tab, based on the navigationAction and User setting - // This is used for manually activated links - func shouldOpenInNewTab(_ navigationAction: WKNavigationAction, webView: WKWebView) -> Bool { + /// Converts a standard YouTube URL to its Duck Player equivalent if applicable. + /// + /// - Parameter url: The YouTube `URL` to convert. + /// - Returns: A Duck Player `URL` if applicable. + func getDuckURLFor(_ url: URL) -> URL { + guard let (youtubeVideoID, timestamp) = url.youtubeVideoParams, + url.isDuckPlayer, + !url.isDuckURLScheme, + duckPlayerMode != .disabled + else { + return url + } + return URL.duckPlayer(youtubeVideoID, timestamp: timestamp) + } + + /// Decides whether to cancel navigation to prevent opening the YouTube app from the web view. + /// + /// - Parameters: + /// - navigationAction: The `WKNavigationAction` to evaluate. + /// - webView: The `WKWebView` where navigation is occurring. + /// - Returns: `true` if the navigation should be canceled, `false` otherwise. + @MainActor + func handleDelegateNavigation(navigationAction: WKNavigationAction, webView: WKWebView) -> Bool { - // let openInNewTab = appSettings.duckPlayerOpenInNewTab - let openInNewTab = appSettings.duckPlayerOpenInNewTab - let isFeatureEnabled = featureFlagger.isFeatureOn(.duckPlayer) - let isSubFeatureEnabled = featureFlagger.isFeatureOn(.duckPlayerOpenInNewTab) || internalUserDecider.isInternalUser - let isDuckPlayer = navigationAction.request.url?.isDuckPlayer ?? false - let isDuckPlayerEnabled = duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk + guard let url = navigationAction.request.url else { + return false + } - if openInNewTab && - isFeatureEnabled && - isSubFeatureEnabled && - isDuckPlayer && - self.navigationType == .linkActivated && - isDuckPlayerEnabled { + // Only account for MainFrame navigation + guard navigationAction.isTargetingMainFrame() else { + return false + } + + // Only if DuckPlayer is enabled + guard isDuckPlayerFeatureEnabled else { + return false + } + + // Only account for in 'Always' mode + if duckPlayerMode == .disabled { + return false + } + + // Only account for in 'Duck Player' URL + if url.isDuckPlayer { + return false + } + + // Do not intercept any back/forward navigation + if navigationAction.navigationType == .backForward { + return false + } + + // Ignore YouTube Watch URLs if allowFirst video is set + if url.isYoutubeWatch && duckPlayer.settings.allowFirstVideo { + return false + } + + // Redirect to Duck Player if enabled + if url.isYoutubeWatch && duckPlayerMode == .enabled && !isDuckPlayerRedirect(url: url) { + redirectToDuckPlayerVideo(url: url, webView: webView) return true } + + // Redirect to Youtube + DuckPlayer Overlay if Ask Mode + if url.isYoutubeWatch && duckPlayerMode == .alwaysAsk && !isDuckPlayerRedirect(url: url) { + redirectToYouTubeVideo(url: url, webView: webView, allowFirstVideo: false) + return true + } + + // Allow everything else return false + } } extension WKWebView { - var isEmptyTab: Bool { - return self.url == nil || self.url?.absoluteString == "about:blank" + /// Returns the count of items in the web view's back navigation list. + @objc func backListItemsCount() -> Int { + return backForwardList.backList.count } + } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandling.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandling.swift index 2c9ce16bd0..1755a54cf2 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandling.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandling.swift @@ -19,26 +19,136 @@ import WebKit -enum DuckPlayerNavigationEvent { - case youtubeVideoPageVisited - case JSTriggeredNavigation +/// Represents the referrer source for the Duck Player. +public enum DuckPlayerReferrer: String { + + case youtube + case youtubeOverlay + case serp + case other + case undefined +} + +extension DuckPlayerReferrer { + /// Initializes a `DuckPlayerReferrer` from a string value. + /// + /// - Parameter string: The string representation of the referrer. + init(string: String) { + self = DuckPlayerReferrer(rawValue: string) ?? .undefined + } +} + +/// Represents the result of handling a URL change in the Duck Player navigation handler. +enum DuckPlayerNavigationHandlerURLChangeResult { + + /// Possible reasons for not handling a URL change. + enum HandlingResult { + case featureOff + case invalidURL + case duckPlayerDisabled + case isNotYoutubeWatch + case disabledForVideo + case duplicateNavigation + } + + case handled + case notHandled(HandlingResult) } +/// Represents the direction of navigation in the Duck Player. +enum DuckPlayerNavigationDirection { + case back + case forward +} + +@MainActor +/// Protocol defining the navigation handling for Duck Player. protocol DuckPlayerNavigationHandling: AnyObject { + + /// The referrer of the Duck Player. var referrer: DuckPlayerReferrer { get set } - var duckPlayer: DuckPlayerProtocol { get } - func handleNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView) - func handleJSNavigation(url: URL?, webView: WKWebView) - func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, - completion: @escaping (WKNavigationActionPolicy) -> Void, - webView: WKWebView) + + /// Delegate for handling tab navigation events. + var tabNavigationHandler: DuckPlayerTabNavigationHandling? { get set } + + /// The DuckPlayer instance used for handling video playback. + var duckPlayer: DuckPlayerControlling { get } + + /// Handles URL changes in the web view. + /// + /// - Parameter webView: The web view where the URL change occurred. + /// - Returns: The result of handling the URL change. + func handleURLChange(webView: WKWebView) -> DuckPlayerNavigationHandlerURLChangeResult + + /// Handles the back navigation action in the web view. + /// + /// - Parameter webView: The web view to navigate back in. func handleGoBack(webView: WKWebView) + + /// Handles the reload action in the web view. + /// + /// - Parameter webView: The web view to reload. func handleReload(webView: WKWebView) + + /// Performs actions when the handler is attached to a web view. + /// + /// - Parameter webView: The web view being attached. func handleAttach(webView: WKWebView) + + /// Handles the start of page loading in the web view. + /// + /// - Parameter webView: The web view that started loading. + func handleDidStartLoading(webView: WKWebView) + + /// Handles the completion of page loading in the web view. + /// + /// - Parameter webView: The web view that finished loading. + func handleDidFinishLoading(webView: WKWebView) + + /// Converts a standard YouTube URL to its Duck Player equivalent if applicable. + /// + /// - Parameter url: The YouTube URL to convert. + /// - Returns: A Duck Player URL if applicable. func getDuckURLFor(_ url: URL) -> URL - func handleEvent(event: DuckPlayerNavigationEvent, - url: URL?, - navigationAction: WKNavigationAction?) - func shouldOpenInNewTab(_ navigationAction: WKNavigationAction, webView: WKWebView) -> Bool + /// Handles navigation actions to Duck Player URLs. + /// + /// - Parameters: + /// - navigationAction: The navigation action to handle. + /// - webView: The web view where navigation is occurring. + func handleDuckNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView) + + /// Decides whether to cancel navigation to prevent opening the YouTube app from the web view. + /// + /// - Parameters: + /// - navigationAction: The navigation action to evaluate. + /// - webView: The web view where navigation is occurring. + /// - Returns: `true` if the navigation should be canceled, `false` otherwise. + func handleDelegateNavigation(navigationAction: WKNavigationAction, webView: WKWebView) -> Bool +} + +/// Protocol defining the tab navigation handling for Duck Player. +protocol DuckPlayerTabNavigationHandling: AnyObject { + /// Opens a new tab for the specified URL. + /// + /// - Parameter url: The URL to open in a new tab. + func openTab(for url: URL) + + /// Closes the current tab. + func closeTab() +} + +/// Protocol defining a navigation action for Duck Player. +protocol NavigationActionProtocol { + + var request: URLRequest { get } + var isTargetingMainFrame: Bool { get } + var navigationType: WKNavigationType { get } +} + +extension WKNavigationAction: NavigationActionProtocol { + /// Indicates whether the navigation action targets the main frame. + var isTargetingMainFrame: Bool { + return self.targetFrame?.isMainFrame ?? false + } } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift b/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift index c650284fcc..2100b563bd 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerSettings.swift @@ -21,13 +21,14 @@ import BrowserServicesKit import Combine import Core +/// Represents the different modes for Duck Player operation. enum DuckPlayerMode: Equatable, Codable, CustomStringConvertible, CaseIterable { case enabled, alwaysAsk, disabled private static let enabledString = "enabled" private static let alwaysAskString = "alwaysAsk" private static let neverString = "disabled" - + var description: String { switch self { case .enabled: @@ -38,7 +39,7 @@ enum DuckPlayerMode: Equatable, Codable, CustomStringConvertible, CaseIterable { return UserText.duckPlayerDisabledLabel } } - + var stringValue: String { switch self { case .enabled: @@ -50,6 +51,9 @@ enum DuckPlayerMode: Equatable, Codable, CustomStringConvertible, CaseIterable { } } + /// Initializes a `DuckPlayerMode` from a string value. + /// + /// - Parameter stringValue: The string representation of the mode. init?(stringValue: String) { switch stringValue { case Self.enabledString: @@ -64,20 +68,46 @@ enum DuckPlayerMode: Equatable, Codable, CustomStringConvertible, CaseIterable { } } +/// Protocol defining the settings for Duck Player. protocol DuckPlayerSettings: AnyObject { + /// Publisher that emits when Duck Player settings change. var duckPlayerSettingsPublisher: AnyPublisher { get } + + /// The current mode of Duck Player. var mode: DuckPlayerMode { get } + + /// Indicates if the "Always Ask" overlay has been hidden. var askModeOverlayHidden: Bool { get } + + /// Flag to allow the first video to play in Youtube var allowFirstVideo: Bool { get set } + /// Determines if Duck Player should open videos in a new tab. + var openInNewTab: Bool { get } + + /// Initializes a new instance with the provided app settings and privacy configuration manager. + /// + /// - Parameters: + /// - appSettings: The application settings. + /// - privacyConfigManager: The privacy configuration manager. init(appSettings: AppSettings, privacyConfigManager: PrivacyConfigurationManaging) + /// Sets the Duck Player mode. + /// + /// - Parameter mode: The mode to set. func setMode(_ mode: DuckPlayerMode) + + /// Sets whether the "Always Ask" overlay has been hidden. + /// + /// - Parameter overlayHidden: A Boolean indicating if the overlay is hidden. func setAskModeOverlayHidden(_ overlayHidden: Bool) + + /// Triggers a notification to update subscribers about settings changes. func triggerNotification() } +/// Default implementation of `DuckPlayerSettings`. final class DuckPlayerSettingsDefault: DuckPlayerSettings { private var appSettings: AppSettings @@ -102,6 +132,11 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { duckPlayerSettingsSubject.eraseToAnyPublisher() } + /// Initializes a new instance with the provided app settings and privacy configuration manager. + /// + /// - Parameters: + /// - appSettings: The application settings. + /// - privacyConfigManager: The privacy configuration manager. init(appSettings: AppSettings = AppDependencyProvider.shared.appSettings, privacyConfigManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager) { self.appSettings = appSettings @@ -111,6 +146,7 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { registerForNotificationChanges() } + /// DuckPlayer features are only available in these domains public struct OriginDomains { static let duckduckgo = "duckduckgo.com" static let youtubeWWW = "www.youtube.com" @@ -118,26 +154,33 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { static let youtubeMobile = "m.youtube.com" } + /// The current mode of Duck Player. var mode: DuckPlayerMode { - let experiment = DuckPlayerLaunchExperiment() - if isFeatureEnabled && experiment.isEnrolled && experiment.isExperimentCohort { + if isFeatureEnabled { return appSettings.duckPlayerMode } else { return .disabled } } + /// Indicates if the "Always Ask" overlay has been hidden. var askModeOverlayHidden: Bool { - let experiment = DuckPlayerLaunchExperiment() - if isFeatureEnabled && experiment.isEnrolled && experiment.isExperimentCohort { + if isFeatureEnabled { return appSettings.duckPlayerAskModeOverlayHidden } else { return false } } + /// Flag to allow the first video to play without redirection. var allowFirstVideo: Bool = false + /// Determines if Duck Player should open videos in a new tab. + var openInNewTab: Bool { + return appSettings.duckPlayerOpenInNewTab + } + + /// Registers a publisher to listen for changes in the privacy configuration. private func registerConfigPublisher() { isFeatureEnabledCancellable = privacyConfigManager.updatesPublisher .map { [weak privacyConfigManager] in @@ -149,6 +192,7 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { } } + /// Registers for notification changes in Duck Player settings. private func registerForNotificationChanges() { NotificationCenter.default.addObserver(self, selector: #selector(publishUpdate), @@ -156,6 +200,9 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { object: nil) } + /// Sets the Duck Player mode. + /// + /// - Parameter mode: The mode to set. func setMode(_ mode: DuckPlayerMode) { if mode != appSettings.duckPlayerMode { appSettings.duckPlayerMode = mode @@ -163,6 +210,9 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { } } + /// Sets whether the "Always Ask" overlay has been hidden. + /// + /// - Parameter overlayHidden: A Boolean indicating if the overlay is hidden. func setAskModeOverlayHidden(_ overlayHidden: Bool) { if overlayHidden != appSettings.duckPlayerAskModeOverlayHidden { appSettings.duckPlayerAskModeOverlayHidden = overlayHidden @@ -170,10 +220,14 @@ final class DuckPlayerSettingsDefault: DuckPlayerSettings { } } + /// Publishes an update notification when settings change. + /// + /// - Parameter notification: The notification received. @objc private func publishUpdate(_ notification: Notification) { triggerNotification() } + /// Triggers a notification to update subscribers about settings changes. func triggerNotification() { duckPlayerSettingsSubject.send() } diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index b3463bbb8d..29e0bed47f 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -28,7 +28,7 @@ import DuckPlayer final class YoutubeOverlayUserScript: NSObject, Subfeature { - var duckPlayer: DuckPlayerProtocol + var duckPlayer: DuckPlayerControlling private var cancellables = Set() var statisticsStore: StatisticsStore private var duckPlayerStorage: DuckPlayerStorage @@ -36,7 +36,7 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { static let featureName = "duckPlayer" } - init(duckPlayer: DuckPlayerProtocol, + init(duckPlayer: DuckPlayerControlling, statisticsStore: StatisticsStore = StatisticsUserDefaults(), duckPlayerStorage: DuckPlayerStorage = DefaultDuckPlayerStorage()) { self.duckPlayer = duckPlayer diff --git a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift index e85f0ee19b..f28e8a46c3 100644 --- a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift @@ -24,7 +24,7 @@ import Combine final class YoutubePlayerUserScript: NSObject, Subfeature { - var duckPlayer: DuckPlayerProtocol + var duckPlayer: DuckPlayerControlling private var cancellables = Set() struct Constants { @@ -37,9 +37,10 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { static let initialSetup = "initialSetup" static let openSettings = "openSettings" static let openInfo = "openInfo" + static let telemetryEvent = "telemetryEvent" } - init(duckPlayer: DuckPlayerProtocol) { + init(duckPlayer: DuckPlayerControlling) { self.duckPlayer = duckPlayer super.init() subscribeToDuckPlayerMode() @@ -79,6 +80,8 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { return duckPlayer.openDuckPlayerSettings case Handlers.openInfo: return duckPlayer.openDuckPlayerInfo + case Handlers.telemetryEvent: + return duckPlayer.telemetryEvent default: assertionFailure("YoutubePlayerUserScript: Failed to parse User Script message: \(methodName)") return nil diff --git a/DuckDuckGo/FavoritesFaviconLoader.swift b/DuckDuckGo/FavoritesFaviconLoader.swift index 7adc516ce9..4716a21a48 100644 --- a/DuckDuckGo/FavoritesFaviconLoader.swift +++ b/DuckDuckGo/FavoritesFaviconLoader.swift @@ -40,8 +40,12 @@ actor FavoritesFaviconLoader: FavoritesFaviconLoading { } tasks[domain] = newTask + let value = await newTask.value + if value == nil { + tasks[domain] = nil + } - return await newTask.value + return value } nonisolated func existingFavicon(for favorite: Favorite, size: CGFloat) -> Favicon? { diff --git a/DuckDuckGo/FavoritesHomeViewSectionRenderer.swift b/DuckDuckGo/FavoritesHomeViewSectionRenderer.swift index 0003ab9127..e4cf90c408 100644 --- a/DuckDuckGo/FavoritesHomeViewSectionRenderer.swift +++ b/DuckDuckGo/FavoritesHomeViewSectionRenderer.swift @@ -34,7 +34,7 @@ protocol FavoritesHomeViewSectionRendererDelegate: AnyObject { favoriteDeleted favorite: BookmarkEntity) } -class FavoritesHomeViewSectionRenderer: NSObject, HomeViewSectionRenderer { +class FavoritesHomeViewSectionRenderer { struct Constants { @@ -43,7 +43,8 @@ class FavoritesHomeViewSectionRenderer: NSObject, HomeViewSectionRenderer { static let defaultHeaderHeight: CGFloat = 20 static let horizontalMargin: CGFloat = 2 static let largeModeMargin: CGFloat = 24 - + static let sideInsets: CGFloat = 25 + } let viewModel: FavoritesListInteracting @@ -83,13 +84,6 @@ class FavoritesHomeViewSectionRenderer: NSObject, HomeViewSectionRenderer { return Constants.defaultHeaderHeight } - func install(into controller: HomeViewController) { - self.controller = controller - if numberOfItems > 0 { - controller.hideLogo() - } - } - func install(into controller: UIViewController & FavoritesHomeViewSectionRendererDelegate) { self.controller = controller } @@ -103,7 +97,7 @@ class FavoritesHomeViewSectionRenderer: NSObject, HomeViewSectionRenderer { if isPad { margin = (collectionView.frame.width - Constants.searchWidthPad) / 2 } else { - let defaultMargin = HomeViewSectionRenderers.Constants.sideInsets + let defaultMargin = FavoritesHomeViewSectionRenderer.Constants.sideInsets let landscapeMargin = (collectionView.frame.width - Constants.searchWidth + defaultMargin) / 2 margin = isPortrait ? defaultMargin : landscapeMargin } diff --git a/DuckDuckGo/FavoriteDataSource.swift b/DuckDuckGo/FavoritesListInteractingAdapter.swift similarity index 72% rename from DuckDuckGo/FavoriteDataSource.swift rename to DuckDuckGo/FavoritesListInteractingAdapter.swift index 921938d532..2f4e3fac30 100644 --- a/DuckDuckGo/FavoriteDataSource.swift +++ b/DuckDuckGo/FavoritesListInteractingAdapter.swift @@ -1,5 +1,5 @@ // -// FavoriteDataSource.swift +// FavoritesListInteractingAdapter.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -24,12 +24,30 @@ import Bookmarks final class FavoritesListInteractingAdapter: NewTabPageFavoriteDataSource { let favoritesListInteracting: FavoritesListInteracting + let appSettings: AppSettings - init(favoritesListInteracting: FavoritesListInteracting) { + private var cancellables: Set = [] + + private var displayModeSubject = PassthroughSubject() + + init(favoritesListInteracting: FavoritesListInteracting, appSettings: AppSettings = AppDependencyProvider.shared.appSettings) { self.favoritesListInteracting = favoritesListInteracting + self.appSettings = appSettings + self.externalUpdates = favoritesListInteracting.externalUpdates.merge(with: displayModeSubject).eraseToAnyPublisher() + + NotificationCenter.default.publisher(for: AppUserDefaults.Notifications.favoritesDisplayModeChange) + .receive(on: DispatchQueue.main) + .sink { [weak self] _ in + guard let self else { + return + } + favoritesListInteracting.favoritesDisplayMode = self.appSettings.favoritesDisplayMode + displayModeSubject.send() + } + .store(in: &cancellables) } - var externalUpdates: AnyPublisher { favoritesListInteracting.externalUpdates } + let externalUpdates: AnyPublisher var favorites: [Favorite] { (try? favoritesListInteracting.favorites.map(Favorite.init)) ?? [] diff --git a/DuckDuckGo/FavoritesViewModel.swift b/DuckDuckGo/FavoritesViewModel.swift index 781d77a044..babd8e20c2 100644 --- a/DuckDuckGo/FavoritesViewModel.swift +++ b/DuckDuckGo/FavoritesViewModel.swift @@ -54,18 +54,23 @@ class FavoritesViewModel: ObservableObject { private let favoriteDataSource: NewTabPageFavoriteDataSource private let pixelFiring: PixelFiring.Type private let dailyPixelFiring: DailyPixelFiring.Type + private let isNewTabPageCustomizationEnabled: Bool var isEmpty: Bool { allFavorites.filter(\.isFavorite).isEmpty } - init(favoriteDataSource: NewTabPageFavoriteDataSource, + init(isNewTabPageCustomizationEnabled: Bool = false, + favoriteDataSource: NewTabPageFavoriteDataSource, faviconLoader: FavoritesFaviconLoading, pixelFiring: PixelFiring.Type = Pixel.self, dailyPixelFiring: DailyPixelFiring.Type = DailyPixel.self) { self.favoriteDataSource = favoriteDataSource self.pixelFiring = pixelFiring self.dailyPixelFiring = dailyPixelFiring + self.isNewTabPageCustomizationEnabled = isNewTabPageCustomizationEnabled + self.isCollapsed = isNewTabPageCustomizationEnabled + self.faviconLoader = MissingFaviconWrapper(loader: faviconLoader, onFaviconMissing: { [weak self] in guard let self else { return } @@ -73,8 +78,7 @@ class FavoritesViewModel: ObservableObject { self.faviconMissing() } }) - - + favoriteDataSource.externalUpdates.sink { [weak self] _ in self?.updateData() }.store(in: &cancellables) @@ -93,6 +97,10 @@ class FavoritesViewModel: ObservableObject { } func prefixedFavorites(for columnsCount: Int) -> FavoritesSlice { + guard isNewTabPageCustomizationEnabled else { + return .init(items: allFavorites, isCollapsible: false) + } + let hasFavorites = allFavorites.contains(where: \.isFavorite) let maxCollapsedItemsCount = hasFavorites ? columnsCount * 2 : columnsCount let isCollapsible = allFavorites.count > maxCollapsedItemsCount @@ -170,7 +178,10 @@ class FavoritesViewModel: ObservableObject { var allFavorites = favoriteDataSource.favorites.map { FavoriteItem.favorite($0) } - allFavorites.append(.addFavorite) + + if isNewTabPageCustomizationEnabled { + allFavorites.append(.addFavorite) + } self.allFavorites = allFavorites } diff --git a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift index b7562586b9..2a4469cdc6 100644 --- a/DuckDuckGo/Feedback/VPNFeedbackFormView.swift +++ b/DuckDuckGo/Feedback/VPNFeedbackFormView.swift @@ -22,7 +22,10 @@ import NetworkProtection struct VPNFeedbackFormCategoryView: View { @Environment(\.dismiss) private var dismiss - let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver) + let collector = DefaultVPNMetadataCollector( + statusObserver: AppDependencyProvider.shared.connectionObserver, + serverInfoObserver: AppDependencyProvider.shared.serverInfoObserver + ) var body: some View { VStack { diff --git a/DuckDuckGo/Feedback/VPNMetadataCollector.swift b/DuckDuckGo/Feedback/VPNMetadataCollector.swift index a2f6b69669..f77dcfddd2 100644 --- a/DuckDuckGo/Feedback/VPNMetadataCollector.swift +++ b/DuckDuckGo/Feedback/VPNMetadataCollector.swift @@ -107,7 +107,7 @@ final class DefaultVPNMetadataCollector: VPNMetadataCollector { private let defaults: UserDefaults init(statusObserver: ConnectionStatusObserver, - serverInfoObserver: ConnectionServerInfoObserver = ConnectionServerInfoObserverThroughSession(), + serverInfoObserver: ConnectionServerInfoObserver, accountManager: AccountManager = AppDependencyProvider.shared.subscriptionManager.accountManager, settings: VPNSettings = .init(defaults: .networkProtectionGroupDefaults), defaults: UserDefaults = .networkProtectionGroupDefaults) { @@ -277,7 +277,10 @@ extension VPNMetadata: UnifiedFeedbackMetadata {} extension DefaultVPNMetadataCollector: UnifiedMetadataCollector { convenience init() { - self.init(statusObserver: AppDependencyProvider.shared.connectionObserver) + self.init( + statusObserver: AppDependencyProvider.shared.connectionObserver, + serverInfoObserver: AppDependencyProvider.shared.serverInfoObserver + ) } func collectMetadata() async -> VPNMetadata? { diff --git a/DuckDuckGo/HomeCollectionView.swift b/DuckDuckGo/HomeCollectionView.swift deleted file mode 100644 index 7368530dc3..0000000000 --- a/DuckDuckGo/HomeCollectionView.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// HomeCollectionView.swift -// DuckDuckGo -// -// Copyright © 2018 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 Bookmarks -import Persistence - -class HomeCollectionView: UICollectionView { - - struct Constants { - static let topInset: CGFloat = 79 - } - - var homePageConfiguration: HomePageConfiguration! - private weak var controller: HomeViewController! - - private(set) var renderers: HomeViewSectionRenderers! - - private lazy var collectionViewReorderingGesture = - UILongPressGestureRecognizer(target: self, action: #selector(self.collectionViewReorderingGestureHandler(gesture:))) - - private var topIndexPath: IndexPath? { - for section in 0.. 0 { - return IndexPath(row: 0, section: section) - } - return nil - } - - override func awakeFromNib() { - super.awakeFromNib() - - register(UINib(nibName: "FavoriteHomeCell", bundle: nil), - forCellWithReuseIdentifier: "favorite") - register(UINib(nibName: "HomeMessageCell", bundle: nil), - forCellWithReuseIdentifier: "homeMessageCell") - - register(HomeMessageCollectionViewCell.self, forCellWithReuseIdentifier: "HomeMessageCell") - - register(EmptyCollectionReusableView.self, - forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, - withReuseIdentifier: EmptyCollectionReusableView.reuseIdentifier) - register(EmptyCollectionReusableView.self, - forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, - withReuseIdentifier: EmptyCollectionReusableView.reuseIdentifier) - - contentInset = UIEdgeInsets(top: Constants.topInset, left: 0, bottom: 0, right: 0) - } - - deinit { - UIMenuController.shared.hideMenu() - } - - func configure(withController controller: HomeViewController, favoritesViewModel: FavoritesListInteracting) { - self.controller = controller - renderers = HomeViewSectionRenderers(controller: controller) - - homePageConfiguration.components(favoritesViewModel: favoritesViewModel).forEach { component in - switch component { - case .navigationBarSearch(let fixed): - renderers.install(renderer: NavigationSearchHomeViewSectionRenderer(fixed: fixed)) - - case .favorites: - let renderer = FavoritesHomeViewSectionRenderer(viewModel: favoritesViewModel) - renderer.onFaviconMissing = { [weak self] _ in - guard let self else { - return - } - - self.controller.faviconsFetcherOnboarding.presentOnboardingIfNeeded(from: self.controller) - } - renderers.install(renderer: renderer) - - case .homeMessage: - renderers.install(renderer: HomeMessageViewSectionRenderer(homePageConfiguration: homePageConfiguration, - privacyProDataReporter: controller.privacyProDataReporter)) - } - - } - - dataSource = renderers - delegate = renderers - dropDelegate = renderers - dragDelegate = renderers - - collectionViewReorderingGesture.delegate = self - addGestureRecognizer(collectionViewReorderingGesture) - } - - func launchNewSearch() { - renderers.launchNewSearch() - } - - func didAppear() { - renderers.didAppear() - } - - @objc func collectionViewReorderingGestureHandler(gesture: UILongPressGestureRecognizer) { - switch gesture.state { - case .began: - if let indexPath = indexPathForItem(at: gesture.location(in: self)) { - UISelectionFeedbackGenerator().selectionChanged() - UIMenuController.shared.hideMenu() - beginInteractiveMovementForItem(at: indexPath) - } - - case .changed: - updateInteractiveMovementTargetPosition(gesture.location(in: gesture.view!)) - - case .ended: - renderers.endReordering() - endInteractiveMovement() - UIImpactFeedbackGenerator().impactOccurred() - if let indexPath = indexPathForItem(at: gesture.location(in: self)) { - // needs to chance to settle in case the model has been updated - DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { - self.showMenu(at: indexPath) - } - } - - default: - cancelInteractiveMovement() - } - } - - private func showMenu(at indexPath: IndexPath) { - guard let menuView = cellForItem(at: indexPath) else { return } - guard menuView.becomeFirstResponder() else { return } - let renderer = renderers.rendererFor(section: indexPath.section) - guard let menuItems = renderer.menuItemsFor(itemAt: indexPath.row) else { return } - - let menuController = UIMenuController.shared - - menuController.menuItems = menuItems - menuController.showMenu(from: self, rect: menuView.frame) - } - - func openedAsNewTab(allowingKeyboard: Bool) { - renderers.openedAsNewTab(allowingKeyboard: allowingKeyboard) - } - - func viewDidTransition(to size: CGSize) { - - if let topIndexPath = topIndexPath { - controller.collectionView.scrollToItem(at: topIndexPath, at: .top, animated: false) - } - controller.collectionView.reloadData() - } - - func refreshHomeConfiguration() { - homePageConfiguration.refresh() - renderers.refresh() - } -} - - -extension HomeCollectionView: UIGestureRecognizerDelegate { - - override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { - if gestureRecognizer == collectionViewReorderingGesture, - let indexPath = indexPathForItem(at: gestureRecognizer.location(in: self)) { - return renderers.rendererFor(section: indexPath.section).supportsReordering() - } - return super.gestureRecognizerShouldBegin(gestureRecognizer) - } -} diff --git a/DuckDuckGo/HomeControllerDelegate.swift b/DuckDuckGo/HomeControllerDelegate.swift deleted file mode 100644 index 2955e0bdb7..0000000000 --- a/DuckDuckGo/HomeControllerDelegate.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// HomeControllerDelegate.swift -// DuckDuckGo -// -// Copyright © 2017 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 Bookmarks - -protocol HomeControllerDelegate: AnyObject { - - func home(_ home: HomeViewController, didRequestUrl url: URL) - - func home(_ home: HomeViewController, didRequestEdit favorite: BookmarkEntity) - - func home(_ home: HomeViewController, didRequestQuery query: String) - - func home(_ home: HomeViewController, didRequestContentOverflow shouldOverflow: Bool) -> CGFloat - - func homeDidDeactivateOmniBar(home: HomeViewController) - - func showSettings(_ home: HomeViewController) - - func home(_ home: HomeViewController, didRequestHideLogo hidden: Bool) - - func homeDidRequestLogoContainer(_ home: HomeViewController) -> UIView - - func home(_ home: HomeViewController, searchTransitionUpdated percent: CGFloat) - -} diff --git a/DuckDuckGo/HomeMessage.xcassets/WidgetEducation/WidgetEducationHomeScreen.imageset/iphone.png b/DuckDuckGo/HomeMessage.xcassets/WidgetEducation/WidgetEducationHomeScreen.imageset/iphone.png deleted file mode 100644 index 19ae85b0b9..0000000000 Binary files a/DuckDuckGo/HomeMessage.xcassets/WidgetEducation/WidgetEducationHomeScreen.imageset/iphone.png and /dev/null differ diff --git a/DuckDuckGo/HomeMessageViewModel.swift b/DuckDuckGo/HomeMessageViewModel.swift index 67acfb3af4..8b5d73b01a 100644 --- a/DuckDuckGo/HomeMessageViewModel.swift +++ b/DuckDuckGo/HomeMessageViewModel.swift @@ -169,3 +169,12 @@ struct HomeMessageButtonViewModel { let action: () async -> Void } + +private extension RemoteAction { + var isShare: Bool { + if case .share = self.actionStyle() { + return true + } + return false + } +} diff --git a/DuckDuckGo/HomeMessageViewSectionRenderer.swift b/DuckDuckGo/HomeMessageViewSectionRenderer.swift deleted file mode 100644 index 4926c5e3b7..0000000000 --- a/DuckDuckGo/HomeMessageViewSectionRenderer.swift +++ /dev/null @@ -1,241 +0,0 @@ -// -// HomeMessageViewSectionRenderer.swift -// DuckDuckGo -// -// Copyright © 2020 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 BrowserServicesKit -import RemoteMessaging -import Common - -protocol HomeMessageViewSectionRendererDelegate: AnyObject { - - func homeMessageRenderer(_ renderer: HomeMessageViewSectionRenderer, - didDismissHomeMessage homeMessage: HomeMessage) - -} - -class HomeMessageViewSectionRenderer: NSObject, HomeViewSectionRenderer { - - struct Constants { - - static let topMargin: CGFloat = 16 - static let horizontalMargin: CGFloat = 16 - - } - - private weak var controller: HomeViewController? - - private let homePageConfiguration: HomePageConfiguration - private let privacyProDataReporter: PrivacyProDataReporting? - - init(homePageConfiguration: HomePageConfiguration, privacyProDataReporter: PrivacyProDataReporting?) { - self.homePageConfiguration = homePageConfiguration - self.privacyProDataReporter = privacyProDataReporter - super.init() - } - - func install(into controller: HomeViewController) { - self.controller = controller - hideLogoIfThereAreMessagesToDisplay() - } - - func refresh() { - hideLogoIfThereAreMessagesToDisplay() - } - - private func hideLogoIfThereAreMessagesToDisplay() { - if !homePageConfiguration.homeMessages.isEmpty { - controller?.hideLogo() - } - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - insetForSectionAt section: Int) -> UIEdgeInsets? { - let widthNotTakenByCell = collectionView.frame.width - collectionViewCellWidth(collectionView) - let horizontalInset = widthNotTakenByCell / 2.0 - - let isEmpty = collectionView.numberOfItems(inSection: section) == 0 - let top = isEmpty ? 0 : Constants.topMargin - - return UIEdgeInsets(top: top, left: horizontalInset, bottom: 0, right: horizontalInset) - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return homePageConfiguration.homeMessages.count - } - - func collectionView(_ collectionView: UICollectionView, - viewForSupplementaryElementOfKind kind: String, - at indexPath: IndexPath) -> UICollectionReusableView { - return collectionView.dequeueReusableSupplementaryView(ofKind: kind, - withReuseIdentifier: EmptyCollectionReusableView.reuseIdentifier, - for: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: HomeMessageCollectionViewCell.reuseIdentifier, - for: indexPath) as? HomeMessageCollectionViewCell else { - fatalError("Could not dequeue cell") - } - configureCell(cell, in: collectionView, at: indexPath) - return cell - } - - private func configureCell(_ cell: HomeMessageCollectionViewCell, - in collectionView: UICollectionView, - at indexPath: IndexPath) { - if let controller = controller, let viewModel = homeMessageViewModel(for: indexPath, collectionView: collectionView) { - cell.configure(with: viewModel, parent: controller) - } - } - - private func homeMessageViewModel(for indexPath: IndexPath, - collectionView: UICollectionView) -> HomeMessageViewModel? { - let message = homePageConfiguration.homeMessages[indexPath.row] - switch message { - case .placeholder: - return HomeMessageViewModel(messageId: "", sendPixels: false, modelType: .small(titleText: "", descriptionText: "")) { [weak self] _ in - self?.dismissHomeMessage(message, at: indexPath, in: collectionView) - } onDidAppear: { - // no-op - } onAttachAdditionalParameters: { _, params in - params - } - case .remoteMessage(let remoteMessage): - let onDidAppear = { [weak self] in - self?.homePageConfiguration.didAppear(message) - } - - // call didAppear here to support marking messages as shown when they appear on the new tab page - // as a result of refreshing a config while the user was on a new tab page already. - onDidAppear() - - return HomeMessageViewModelBuilder.build(for: remoteMessage, with: privacyProDataReporter) { [weak self] action in - - guard let action, - let self else { return } - - switch action { - - case .action(let isSharing): - if !isSharing { - self.dismissHomeMessage(message, at: indexPath, in: collectionView) - } - if remoteMessage.isMetricsEnabled { - Pixel.fire(pixel: .remoteMessageActionClicked, - withAdditionalParameters: self.additionalParameters(for: remoteMessage.id)) - } - - case .primaryAction(let isSharing): - if !isSharing { - self.dismissHomeMessage(message, at: indexPath, in: collectionView) - } - if remoteMessage.isMetricsEnabled { - Pixel.fire(pixel: .remoteMessagePrimaryActionClicked, - withAdditionalParameters: self.additionalParameters(for: remoteMessage.id)) - } - - case .secondaryAction(let isSharing): - if !isSharing { - self.dismissHomeMessage(message, at: indexPath, in: collectionView) - } - if remoteMessage.isMetricsEnabled { - Pixel.fire(pixel: .remoteMessageSecondaryActionClicked, - withAdditionalParameters: self.additionalParameters(for: remoteMessage.id)) - } - - case .close: - self.dismissHomeMessage(message, at: indexPath, in: collectionView) - if remoteMessage.isMetricsEnabled { - Pixel.fire(pixel: .remoteMessageDismissed, - withAdditionalParameters: self.additionalParameters(for: remoteMessage.id)) - } - - } - } onDidAppear: { - onDidAppear() - } - } - } - - private func additionalParameters(for messageID: String) -> [String: String] { - let defaultParameters = [PixelParameters.message: "\(messageID)"] - return privacyProDataReporter?.mergeRandomizedParameters(for: .messageID(messageID), - with: defaultParameters) ?? defaultParameters - } - - private func dismissHomeMessage(_ message: HomeMessage, - at indexPath: IndexPath, - in collectionView: UICollectionView) { - Task { @MainActor in - await homePageConfiguration.dismissHomeMessage(message) - animateCellDismissal(at: indexPath, in: collectionView) { - self.controller?.homeMessageRenderer(self, didDismissHomeMessage: message) - } - } - } - - private func animateCellDismissal(at indexPath: IndexPath, - in collectionView: UICollectionView, - completion: @escaping () -> Void) { - guard let cell = collectionView.cellForItem(at: indexPath) else { - completion() - return - } - - UIView.animate(withDuration: 0.3, animations: { - cell.alpha = 0 - }, completion: { _ in - completion() - }) - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - let cell = HomeMessageCollectionViewCell() - configureCell(cell, in: collectionView, at: indexPath) - let size = cell.host?.sizeThatFits(in: CGSize(width: collectionViewCellWidth(collectionView), - height: CGFloat.greatestFiniteMagnitude)) ?? .zero - return size - } - - private func collectionViewCellWidth(_ collectionView: UICollectionView) -> CGFloat { - let marginWidth = Constants.horizontalMargin * 2 - let availableWidth = collectionView.safeAreaLayoutGuide.layoutFrame.width - marginWidth - let maxCellWidth = isPad ? HomeMessageCollectionViewCell.maximumWidthPad : HomeMessageCollectionViewCell.maximumWidth - return min(availableWidth, maxCellWidth) - } - - private var isPad: Bool { - return controller?.traitCollection.horizontalSizeClass == .regular - } -} - -extension RemoteAction { - - var isShare: Bool { - if case .share = self.actionStyle() { - return true - } - return false - } - -} diff --git a/DuckDuckGo/HomePageConfiguration.swift b/DuckDuckGo/HomePageConfiguration.swift index f476642bfc..0c4a1d13a3 100644 --- a/DuckDuckGo/HomePageConfiguration.swift +++ b/DuckDuckGo/HomePageConfiguration.swift @@ -27,21 +27,6 @@ import os.log final class HomePageConfiguration: HomePageMessagesConfiguration { - enum Component: Equatable { - case navigationBarSearch(fixed: Bool) - case favorites - case homeMessage - } - - func components(favoritesViewModel: FavoritesListInteracting) -> [Component] { - let fixed = favoritesViewModel.favorites.count == 0 - return [ - .navigationBarSearch(fixed: fixed), - .homeMessage, - .favorites - ] - } - // MARK: - Messages private var homeMessageStorage: HomeMessageStorage diff --git a/DuckDuckGo/HomeScreenTransition.swift b/DuckDuckGo/HomeScreenTransition.swift index 8de6d19b3d..6fb2e8518b 100644 --- a/DuckDuckGo/HomeScreenTransition.swift +++ b/DuckDuckGo/HomeScreenTransition.swift @@ -90,7 +90,7 @@ class FromHomeScreenTransition: HomeScreenTransition { tabSwitcherViewController.view.frame = transitionContext.finalFrame(for: tabSwitcherViewController) tabSwitcherViewController.prepareForPresentation() - guard let homeScreen = mainViewController.homeController, + guard let homeScreen = mainViewController.newTabPageViewController, let tab = mainViewController.tabManager.model.currentTab, let rowIndex = tabSwitcherViewController.tabsModel.indexOf(tab: tab), let layoutAttr = tabSwitcherViewController.collectionView.layoutAttributesForItem(at: IndexPath(row: rowIndex, section: 0)) @@ -163,7 +163,7 @@ class ToHomeScreenTransition: HomeScreenTransition { prepareSubviews(using: transitionContext) guard let mainViewController = transitionContext.viewController(forKey: .to) as? MainViewController, - let homeScreen = mainViewController.homeController, + let homeScreen = mainViewController.newTabPageViewController, let tab = mainViewController.tabManager.model.currentTab, let rowIndex = tabSwitcherViewController.tabsModel.indexOf(tab: tab), let layoutAttr = tabSwitcherViewController.collectionView.layoutAttributesForItem(at: IndexPath(row: rowIndex, section: 0)) diff --git a/DuckDuckGo/HomeViewController+DaxDialogs.swift b/DuckDuckGo/HomeViewController+DaxDialogs.swift deleted file mode 100644 index 5f22666e74..0000000000 --- a/DuckDuckGo/HomeViewController+DaxDialogs.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// HomeViewController+DaxDialogs.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 -import Core -import SwiftUI - -extension HomeViewController { - - func showNextDaxDialog(dialogProvider: NewTabDialogSpecProvider) { - guard let spec = dialogProvider.nextHomeScreenMessage() else { return } - guard !isShowingDax else { return } - guard let daxDialogViewController = daxDialogViewController else { return } - collectionView.isHidden = true - daxDialogContainer.isHidden = false - daxDialogContainer.alpha = 0.0 - - daxDialogViewController.loadViewIfNeeded() - daxDialogViewController.message = spec.message - daxDialogViewController.accessibleMessage = spec.accessibilityLabel - - if spec == .initial { - UniquePixel.fire(pixel: .onboardingContextualTryVisitSiteUnique, includedParameters: [.appVersion, .atb]) - } - - view.addGestureRecognizer(daxDialogViewController.tapToCompleteGestureRecognizer) - - daxDialogContainerHeight.constant = daxDialogViewController.calculateHeight() - hideLogo() - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - UIView.animate(withDuration: 0.4, animations: { - self.daxDialogContainer.alpha = 1.0 - }, completion: { _ in - self.daxDialogViewController?.start() - }) - } - - configureCollectionView() - } - - func showNextDaxDialogNew(dialogProvider: NewTabDialogSpecProvider, factory: any NewTabDaxDialogProvider) { - dismissHostingController(didFinishNTPOnboarding: false) - let onDismiss = { - dialogProvider.dismiss() - self.dismissHostingController(didFinishNTPOnboarding: true) - } - guard let spec = dialogProvider.nextHomeScreenMessageNew() else { return } - let daxDialogView = AnyView(factory.createDaxDialog(for: spec, onDismiss: onDismiss)) - hostingController = UIHostingController(rootView: daxDialogView) - guard let hostingController else { return } - hostingController.view.backgroundColor = .clear - addChild(hostingController) - view.addSubview(hostingController.view) - hostingController.view.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), - hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), - hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), - hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) - ]) - hostingController.didMove(toParent: self) - hideLogo() - configureCollectionView() - } - - private func dismissHostingController(didFinishNTPOnboarding: Bool) { - hostingController?.willMove(toParent: nil) - hostingController?.view.removeFromSuperview() - hostingController?.removeFromParent() - if didFinishNTPOnboarding { - // If there are favorites to show hide the Dax logo - delegate?.home(self, didRequestHideLogo: hasFavoritesToShow) - } - } -} diff --git a/DuckDuckGo/HomeViewController.swift b/DuckDuckGo/HomeViewController.swift deleted file mode 100644 index 0ff2977bb3..0000000000 --- a/DuckDuckGo/HomeViewController.swift +++ /dev/null @@ -1,380 +0,0 @@ -// -// HomeViewController.swift -// DuckDuckGo -// -// Copyright © 2017 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 Bookmarks -import Combine -import Common -import DDGSync -import Persistence -import RemoteMessaging -import SwiftUI -import BrowserServicesKit -import os.log - -class HomeViewController: UIViewController, NewTabPage { - - @IBOutlet weak var ctaContainerBottom: NSLayoutConstraint! - @IBOutlet weak var ctaContainer: UIView! - - @IBOutlet weak var collectionView: HomeCollectionView! - - @IBOutlet weak var daxDialogContainer: UIView! - @IBOutlet weak var daxDialogContainerHeight: NSLayoutConstraint! - weak var daxDialogViewController: DaxDialogViewController? - var hostingController: UIHostingController? - - var logoContainer: UIView! { - return delegate?.homeDidRequestLogoContainer(self) - } - - var searchHeaderTransition: CGFloat = 0.0 { - didSet { - let percent = searchHeaderTransition > 0.99 ? searchHeaderTransition : 0.0 - - // hide the keyboard if transitioning away - if oldValue == 1.0 && searchHeaderTransition != 1.0 { - chromeDelegate?.omniBar.resignFirstResponder() - } - - delegate?.home(self, searchTransitionUpdated: percent) - chromeDelegate?.omniBar.alpha = percent - chromeDelegate?.tabBarContainer.alpha = percent - } - } - - var isDragging: Bool { - collectionView.isDragging - } - - weak var delegate: HomeControllerDelegate? - weak var chromeDelegate: BrowserChromeDelegate? - - private var viewHasAppeared = false - private var defaultVerticalAlignConstant: CGFloat = 0 - - private let homePageConfiguration: HomePageConfiguration - private let tabModel: Tab - private let favoritesViewModel: FavoritesListInteracting - private let appSettings: AppSettings - private let syncService: DDGSyncing - private let syncDataProviders: SyncDataProviders - private let variantManager: VariantManager - private let newTabDialogFactory: any NewTabDaxDialogProvider - private let newTabDialogTypeProvider: NewTabDialogSpecProvider - private var viewModelCancellable: AnyCancellable? - private var favoritesDisplayModeCancellable: AnyCancellable? - - let privacyProDataReporter: PrivacyProDataReporting - - var hasFavoritesToShow: Bool { - !favoritesViewModel.favorites.isEmpty - } - - static func loadFromStoryboard( - homePageDependecies: HomePageDependencies - ) -> HomeViewController { - let storyboard = UIStoryboard(name: "Home", bundle: nil) - let controller = storyboard.instantiateViewController(identifier: "HomeViewController", creator: { coder in - HomeViewController( - coder: coder, - homePageConfiguration: homePageDependecies.homePageConfiguration, - tabModel: homePageDependecies.model, - favoritesViewModel: homePageDependecies.favoritesViewModel, - appSettings: homePageDependecies.appSettings, - syncService: homePageDependecies.syncService, - syncDataProviders: homePageDependecies.syncDataProviders, - privacyProDataReporter: homePageDependecies.privacyProDataReporter, - variantManager: homePageDependecies.variantManager, - newTabDialogFactory: homePageDependecies.newTabDialogFactory, - newTabDialogTypeProvider: homePageDependecies.newTabDialogTypeProvider - ) - }) - return controller - } - - required init?( - coder: NSCoder, - homePageConfiguration: HomePageConfiguration, - tabModel: Tab, - favoritesViewModel: FavoritesListInteracting, - appSettings: AppSettings, - syncService: DDGSyncing, - syncDataProviders: SyncDataProviders, - privacyProDataReporter: PrivacyProDataReporting, - variantManager: VariantManager, - newTabDialogFactory: any NewTabDaxDialogProvider, - newTabDialogTypeProvider: NewTabDialogSpecProvider - ) { - self.homePageConfiguration = homePageConfiguration - self.tabModel = tabModel - self.favoritesViewModel = favoritesViewModel - self.appSettings = appSettings - self.syncService = syncService - self.syncDataProviders = syncDataProviders - self.privacyProDataReporter = privacyProDataReporter - self.variantManager = variantManager - self.newTabDialogFactory = newTabDialogFactory - self.newTabDialogTypeProvider = newTabDialogTypeProvider - - super.init(coder: coder) - } - - required init?(coder: NSCoder) { - fatalError("Not implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - - NotificationCenter.default.addObserver(self, selector: #selector(HomeViewController.onKeyboardChangeFrame), - name: UIResponder.keyboardWillChangeFrameNotification, object: nil) - - collectionView.homePageConfiguration = homePageConfiguration - configureCollectionView() - - NotificationCenter.default.addObserver(self, - selector: #selector(remoteMessagesDidChange), - name: RemoteMessagingStore.Notifications.remoteMessagesDidChange, - object: nil) - - registerForBookmarksChanges() - } - - private func registerForBookmarksChanges() { - viewModelCancellable = favoritesViewModel.externalUpdates.sink { [weak self] _ in - guard let self = self else { return } - self.bookmarksDidChange() - if self.favoritesViewModel.favorites.isEmpty { - self.delegate?.home(self, didRequestHideLogo: false) - } - } - - favoritesDisplayModeCancellable = NotificationCenter.default.publisher(for: AppUserDefaults.Notifications.favoritesDisplayModeChange) - .receive(on: DispatchQueue.main) - .sink { [weak self] _ in - guard let self else { - return - } - self.favoritesViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode - self.collectionView.reloadData() - } - } - - @objc func bookmarksDidChange() { - configureCollectionView() - } - - @objc func remoteMessagesDidChange() { - DispatchQueue.main.async { - Logger.remoteMessaging.info("Remote messages did change") - self.collectionView.refreshHomeConfiguration() - self.refresh() - } - } - - func configureCollectionView() { - collectionView.configure(withController: self, favoritesViewModel: favoritesViewModel) - } - - func enableContentUnderflow() -> CGFloat { - return delegate?.home(self, didRequestContentOverflow: true) ?? 0 - } - - @discardableResult - func disableContentUnderflow() -> CGFloat { - return delegate?.home(self, didRequestContentOverflow: false) ?? 0 - } - - override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { - super.viewWillTransition(to: size, with: coordinator) - coordinator.animate(alongsideTransition: { _ in - self.collectionView.viewDidTransition(to: size) - }) - self.collectionView.collectionViewLayout.invalidateLayout() - } - - func refresh() { - collectionView.reloadData() - } - - func openedAsNewTab(allowingKeyboard: Bool) { - collectionView.openedAsNewTab(allowingKeyboard: allowingKeyboard) - if !variantManager.isContextualDaxDialogsEnabled { - // In the new onboarding this gets called twice (viewDidAppear in Tab) which then reset the spec to nil. - presentNextDaxDialog() - } - } - - @IBAction func launchSettings() { - delegate?.showSettings(self) - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - // 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) - sendDailyDisplayPixel() - - presentNextDaxDialog() - - collectionView.didAppear() - - viewHasAppeared = true - tabModel.viewed = true - } - - var isShowingDax: Bool { - return !daxDialogContainer.isHidden - } - - func hideLogo() { - delegate?.home(self, didRequestHideLogo: true) - } - - func onboardingCompleted() { - presentNextDaxDialog() - } - - func presentNextDaxDialog() { - if variantManager.isContextualDaxDialogsEnabled { - showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory) - } else { - showNextDaxDialog(dialogProvider: newTabDialogTypeProvider) - } - } - - func showNextDaxDialog() { - presentNextDaxDialog() - } - - func reloadFavorites() { - collectionView.reloadData() - } - - override func prepare(for segue: UIStoryboardSegue, sender: Any?) { - super.prepare(for: segue, sender: sender) - - if segue.destination is DaxDialogViewController { - self.daxDialogViewController = segue.destination as? DaxDialogViewController - } - - } - - @IBAction func hideKeyboard() { - // without this the keyboard hides instantly and abruptly - UIView.animate(withDuration: 0.5) { - self.chromeDelegate?.omniBar.resignFirstResponder() - } - } - - @objc func onKeyboardChangeFrame(notification: NSNotification) { - guard let beginFrame = notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? CGRect else { return } - guard let endFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } - guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return } - - let diff = beginFrame.origin.y - endFrame.origin.y - - if diff > 0 { - ctaContainerBottom.constant = endFrame.size.height - (chromeDelegate?.toolbarHeight ?? 0) - } else { - ctaContainerBottom.constant = 0 - } - - view.setNeedsUpdateConstraints() - - if viewHasAppeared { - UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() } - } - } - - func load(url: URL) { - delegate?.home(self, didRequestUrl: url) - } - - func dismiss() { - delegate = nil - chromeDelegate = nil - removeFromParent() - view.removeFromSuperview() - } - - func launchNewSearch() { - collectionView.launchNewSearch() - } - - private(set) lazy var faviconsFetcherOnboarding: FaviconsFetcherOnboarding = - .init(syncService: syncService, syncBookmarksAdapter: syncDataProviders.bookmarksAdapter) -} - -private extension HomeViewController { - func sendDailyDisplayPixel() { - - let favoritesCount = favoritesViewModel.favorites.count - let bucket = HomePageDisplayDailyPixelBucket(favoritesCount: favoritesCount) - - DailyPixel.fire(pixel: .newTabPageDisplayedDaily, withAdditionalParameters: ["FavoriteCount": bucket.value]) - } -} - -extension HomeViewController: FavoritesHomeViewSectionRendererDelegate { - - func favoritesRenderer(_ renderer: FavoritesHomeViewSectionRenderer, didSelect favorite: BookmarkEntity) { - guard let url = favorite.urlObject else { return } - Pixel.fire(pixel: .favoriteLaunchedNTP) - DailyPixel.fire(pixel: .favoriteLaunchedNTPDaily) - Favicons.shared.loadFavicon(forDomain: url.host, intoCache: .fireproof, fromCache: .tabs) - delegate?.home(self, didRequestUrl: url) - } - - func favoritesRenderer(_ renderer: FavoritesHomeViewSectionRenderer, didRequestEdit favorite: BookmarkEntity) { - delegate?.home(self, didRequestEdit: favorite) - } - - func favoritesRenderer(_ renderer: FavoritesHomeViewSectionRenderer, favoriteDeleted favorite: BookmarkEntity) { - delegate?.home(self, didRequestHideLogo: renderer.viewModel.favorites.count > 0) - } - -} - -extension HomeViewController: HomeMessageViewSectionRendererDelegate { - - func homeMessageRenderer(_ renderer: HomeMessageViewSectionRenderer, didDismissHomeMessage homeMessage: HomeMessage) { - refresh() - } -} - -extension HomeViewController: HomeScreenTransitionSource { - var snapshotView: UIView { - if let logoContainer = logoContainer, !logoContainer.isHidden { - return logoContainer - } else { - return collectionView - } - } - - var rootContainerView: UIView { - collectionView - } -} diff --git a/DuckDuckGo/HomeViewSectionRenderers.swift b/DuckDuckGo/HomeViewSectionRenderers.swift deleted file mode 100644 index 191f323c53..0000000000 --- a/DuckDuckGo/HomeViewSectionRenderers.swift +++ /dev/null @@ -1,269 +0,0 @@ -// -// HomeViewSectionRenderers.swift -// DuckDuckGo -// -// Copyright © 2018 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 - -protocol HomeViewSectionRenderer: AnyObject { - - // MARK: required - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int - - func collectionView(_ collectionView: UICollectionView, - cellForItemAt indexPath: IndexPath) -> UICollectionViewCell - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize - - // MARK: optional - - func install(into controller: HomeViewController) - - func remove(from controller: HomeViewController) - - func openedAsNewTab(allowingKeyboard: Bool) - - func launchNewSearch() - - func supportsReordering() -> Bool - - func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForHeaderInSection section: Int) -> CGSize? - - func scrollViewDidScroll(_ scrollView: UIScrollView) - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForFooterInSection section: Int) -> CGSize? - - func collectionView(_ collectionView: UICollectionView, - viewForSupplementaryElementOfKind kind: String, - at indexPath: IndexPath) -> UICollectionReusableView - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - insetForSectionAt section: Int) -> UIEdgeInsets? - - func collectionView(_ collectionView: UICollectionView, - contextMenuConfigurationForItemAt indexPath: IndexPath, - point: CGPoint) -> UIContextMenuConfiguration? - - func collectionView(_ collectionView: UICollectionView, - previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? - - func collectionView(_ collectionView: UICollectionView, - previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? - - func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] - - func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) - - func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal - - func endReordering() - - func didAppear() - -} - -/// Each renderer added becomes a section in the containing collection view. -class HomeViewSectionRenderers: NSObject, - UICollectionViewDelegate, - UICollectionViewDelegateFlowLayout, - UICollectionViewDataSource, - UICollectionViewDragDelegate, - UICollectionViewDropDelegate { - - struct Constants { - - static let sideInsets: CGFloat = 25 - - } - - private weak var controller: HomeViewController! - private var renderers = [HomeViewSectionRenderer]() - - init(controller: HomeViewController) { - self.controller = controller - super.init() - } - - func didAppear() { - renderers.forEach { - $0.didAppear() - } - } - - func install(renderer: HomeViewSectionRenderer) { - renderer.install(into: controller) - renderers.append(renderer) - } - - func remove(renderer: HomeViewSectionRenderer) -> Int? { - renderer.remove(from: controller) - guard let index = (renderers.firstIndex { $0 === renderer }) else { - return nil - } - renderers.remove(at: index) - return index - } - - func rendererFor(section: Int) -> HomeViewSectionRenderer { - return renderers[section] - } - - func openedAsNewTab(allowingKeyboard: Bool) { - renderers.forEach { renderer in - renderer.openedAsNewTab(allowingKeyboard: allowingKeyboard) - } - } - - func launchNewSearch() { - renderers.forEach { renderer in - renderer.launchNewSearch() - } - } - - func endReordering() { - renderers.forEach { renderer in - renderer.endReordering() - } - } - - func refresh() { - renderers.forEach { renderer in - if let renderer = renderer as? HomeMessageViewSectionRenderer { - renderer.refresh() - } - } - } - - // MARK: UIScrollViewDelegate - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - renderers.forEach { - $0.scrollViewDidScroll(scrollView) - } - } - - // MARK: UICollectionViewDataSource - - func numberOfSections(in collectionView: UICollectionView) -> Int { - return renderers.count - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return renderers[section].collectionView(collectionView, numberOfItemsInSection: section) - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - return renderers[indexPath.section].collectionView(collectionView, cellForItemAt: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, - viewForSupplementaryElementOfKind kind: String, - at indexPath: IndexPath) -> UICollectionReusableView { - return renderers[indexPath.section].collectionView(collectionView, - viewForSupplementaryElementOfKind: kind, - at: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { - return renderers[indexPath.section].collectionView(collectionView, shouldSelectItemAt: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - renderers[indexPath.section].collectionView(collectionView, didSelectItemAt: indexPath) - } - - // MARK: UICollectionViewDelegate - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForHeaderInSection section: Int) -> CGSize { - return renderers[section].collectionView(collectionView, layout: collectionViewLayout, referenceSizeForHeaderInSection: section) - ?? CGSize.zero - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForFooterInSection section: Int) -> CGSize { - return renderers[section].collectionView(collectionView, layout: collectionViewLayout, referenceSizeForFooterInSection: section) - ?? CGSize.zero - } - - func collectionView(_ collectionView: UICollectionView, contextMenuConfigurationForItemAt indexPath: IndexPath, point: CGPoint) -> UIContextMenuConfiguration? { - return renderers[indexPath.section].collectionView(collectionView, - contextMenuConfigurationForItemAt: indexPath, - point: point) - } - - func collectionView(_ collectionView: UICollectionView, - previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { - guard let indexPath = configuration.identifier as? IndexPath else { return nil } - return renderers[indexPath.section].collectionView(collectionView, - previewForDismissingContextMenuWithConfiguration: configuration) - } - - func collectionView(_ collectionView: UICollectionView, - previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { - guard let indexPath = configuration.identifier as? IndexPath else { return nil } - return renderers[indexPath.section].collectionView(collectionView, - previewForHighlightingContextMenuWithConfiguration: configuration) - } - - // MARK: UICollectionViewDelegateFlowLayout - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) - -> CGSize { - return renderers[indexPath.section].collectionView(collectionView, layout: collectionViewLayout, sizeForItemAt: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) - -> UIEdgeInsets { - - return renderers[section].collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: section) ?? - UIEdgeInsets(top: 0, left: Constants.sideInsets, bottom: 0, right: Constants.sideInsets) - } - - // MARK: Drag and Drop - - func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - return renderers[indexPath.section].collectionView(collectionView, itemsForBeginning: session, at: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { - guard let section = coordinator.destinationIndexPath?.section else { return } - renderers[section].collectionView(collectionView, performDropWith: coordinator) - } - - func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { - guard let section = destinationIndexPath?.section else { - return UICollectionViewDropProposal(operation: .forbidden) - } - return renderers[section].collectionView(collectionView, dropSessionDidUpdate: session, withDestinationIndexPath: destinationIndexPath) - } - -} diff --git a/DuckDuckGo/HomeViewSectionRenderersExtension.swift b/DuckDuckGo/HomeViewSectionRenderersExtension.swift deleted file mode 100644 index 6c101e702a..0000000000 --- a/DuckDuckGo/HomeViewSectionRenderersExtension.swift +++ /dev/null @@ -1,119 +0,0 @@ -// -// HomeViewSectionRenderersExtension.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 - -/// An extension that implements the protocol so that other classes don't need to implement everything. -extension HomeViewSectionRenderer { - - func install(into controller: HomeViewController) { } - - func remove(from controller: HomeViewController) { } - - func openedAsNewTab(allowingKeyboard: Bool) { } - - func menuItemsFor(itemAt: Int) -> [UIMenuItem]? { - return nil - } - - func launchNewSearch() { } - - func supportsReordering() -> Bool { return false } - - func collectionView(_ collectionView: UICollectionView, - canMoveItemAt indexPath: IndexPath) -> Bool { - return false - } - - func collectionView(_ collectionView: UICollectionView, - moveItemAt sourceIndexPath: IndexPath, - to destinationIndexPath: IndexPath) { } - - func collectionView(_ collectionView: UICollectionView, - targetIndexPathForMoveFromItemAt originalIndexPath: IndexPath, - toProposedIndexPath proposedIndexPath: IndexPath) -> IndexPath? { - return nil - } - - func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { - return false - } - - func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForHeaderInSection section: Int) -> CGSize? { - return nil - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - referenceSizeForFooterInSection section: Int) -> CGSize? { - return nil - } - - func collectionView(_ collectionView: UICollectionView, - viewForSupplementaryElementOfKind kind: String, - at indexPath: IndexPath) -> UICollectionReusableView { - return collectionView.dequeueReusableSupplementaryView(ofKind: kind, - withReuseIdentifier: EmptyCollectionReusableView.reuseIdentifier, - for: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - insetForSectionAt section: Int) -> UIEdgeInsets? { - return nil - } - - func collectionView(_ collectionView: UICollectionView, - contextMenuConfigurationForItemAt indexPath: IndexPath, - point: CGPoint) -> UIContextMenuConfiguration? { - return nil - } - - - func collectionView(_ collectionView: UICollectionView, - previewForDismissingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { - return nil - } - - func collectionView(_ collectionView: UICollectionView, - previewForHighlightingContextMenuWithConfiguration configuration: UIContextMenuConfiguration) -> UITargetedPreview? { - return nil - } - - func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] { - return [] - } - - func collectionView(_ collectionView: UICollectionView, performDropWith coordinator: UICollectionViewDropCoordinator) { - } - - func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal { - return UICollectionViewDropProposal(operation: .forbidden) - } - - func endReordering() { } - - func didAppear() { } -} diff --git a/DuckDuckGo/HomePageDependencies.swift b/DuckDuckGo/Logger+Onboarding.swift similarity index 53% rename from DuckDuckGo/HomePageDependencies.swift rename to DuckDuckGo/Logger+Onboarding.swift index a806afb709..d8d774a9f3 100644 --- a/DuckDuckGo/HomePageDependencies.swift +++ b/DuckDuckGo/Logger+Onboarding.swift @@ -1,5 +1,5 @@ // -// HomePageDependencies.swift +// Logger+Onboarding.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -18,20 +18,8 @@ // import Foundation -import Bookmarks -import DDGSync -import Core -import BrowserServicesKit +@_exported import os.log -struct HomePageDependencies { - let homePageConfiguration: HomePageConfiguration - let model: Tab - let favoritesViewModel: FavoritesListInteracting - let appSettings: AppSettings - let syncService: DDGSyncing - let syncDataProviders: SyncDataProviders - let privacyProDataReporter: PrivacyProDataReporting - let variantManager: VariantManager - let newTabDialogFactory: any NewTabDaxDialogProvider - let newTabDialogTypeProvider: NewTabDialogSpecProvider +extension Logger { + static var onboarding = { Logger(subsystem: "Onboarding", category: "") }() } diff --git a/DuckDuckGo/LottieView.swift b/DuckDuckGo/LottieView.swift index a3f0a12f59..cb8b04ac6c 100644 --- a/DuckDuckGo/LottieView.swift +++ b/DuckDuckGo/LottieView.swift @@ -35,26 +35,48 @@ struct LottieView: UIViewRepresentable { case withIntro(LoopWithIntroTiming) } + struct ValueProvider { + let provider: AnyValueProvider + let keypath: AnimationKeypath + } + let delay: TimeInterval var isAnimating: Binding private let loopMode: LoopMode + private let animationImageProvider: AnimationImageProvider? + private let valueProvider: ValueProvider? let animationName: String let animation: LottieAnimation? let animationView = LottieAnimationView() - init(lottieFile: String, delay: TimeInterval = 0, loopMode: LoopMode = .mode(.playOnce), isAnimating: Binding = .constant(true)) { + init( + lottieFile: String, + delay: TimeInterval = 0, + loopMode: LoopMode = .mode(.playOnce), + isAnimating: Binding = .constant(true), + animationImageProvider: AnimationImageProvider? = nil, + valueProvider: ValueProvider? = nil + ) { self.animationName = lottieFile self.animation = LottieAnimation.named(lottieFile) self.delay = delay self.isAnimating = isAnimating self.loopMode = loopMode + self.animationImageProvider = animationImageProvider + self.valueProvider = valueProvider } func makeUIView(context: Context) -> some LottieAnimationView { animationView.animation = animation animationView.contentMode = .scaleAspectFit animationView.clipsToBounds = false + if let animationImageProvider { + animationView.imageProvider = animationImageProvider + } + if let valueProvider { + animationView.setValueProvider(valueProvider.provider, keypath: valueProvider.keypath) + } switch loopMode { case .mode(let lottieLoopMode): animationView.loopMode = lottieLoopMode diff --git a/DuckDuckGo/MainViewController+KeyCommands.swift b/DuckDuckGo/MainViewController+KeyCommands.swift index 45f499a9b6..3e6bb42e7c 100644 --- a/DuckDuckGo/MainViewController+KeyCommands.swift +++ b/DuckDuckGo/MainViewController+KeyCommands.swift @@ -33,7 +33,7 @@ extension MainViewController { } var browsingCommands = [UIKeyCommand]() - if homeController == nil { + if newTabPageViewController == nil { browsingCommands = [ UIKeyCommand(title: "", action: #selector(keyboardFind), input: "f", modifierFlags: [.command], discoverabilityTitle: UserText.keyCommandFind), @@ -140,7 +140,7 @@ extension MainViewController { @objc func keyboardLocation() { guard tabSwitcherController == nil else { return } - if let controller = homeController { + if let controller = newTabPageViewController { controller.launchNewSearch() } else { showBars() diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 7e90bf0129..d1a2fd2e36 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -77,12 +77,8 @@ class MainViewController: UIViewController { emailManager.requestDelegate = self return emailManager }() - - var homeViewController: HomeViewController? + var newTabPageViewController: NewTabPageViewController? - var homeController: (NewTabPage & HomeScreenTransitionSource)? { - homeViewController ?? newTabPageViewController - } var tabsBarController: TabsBarViewController? var suggestionTrayController: SuggestionTrayViewController? @@ -127,6 +123,7 @@ class MainViewController: UIViewController { private var feedbackCancellable: AnyCancellable? let subscriptionFeatureAvailability: SubscriptionFeatureAvailability + private let subscriptionCookieManager: SubscriptionCookieManaging let privacyProDataReporter: PrivacyProDataReporting private lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger @@ -199,7 +196,8 @@ class MainViewController: UIViewController { tutorialSettings: TutorialSettings = DefaultTutorialSettings(), statisticsStore: StatisticsStore = StatisticsUserDefaults(), subscriptionFeatureAvailability: SubscriptionFeatureAvailability, - voiceSearchHelper: VoiceSearchHelperProtocol + voiceSearchHelper: VoiceSearchHelperProtocol, + subscriptionCookieManager: SubscriptionCookieManaging ) { self.bookmarksDatabase = bookmarksDatabase self.bookmarksDatabaseCleaner = bookmarksDatabaseCleaner @@ -221,7 +219,8 @@ class MainViewController: UIViewController { privacyProDataReporter: privacyProDataReporter, contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, - onboardingPixelReporter: contextualOnboardingPixelReporter) + onboardingPixelReporter: contextualOnboardingPixelReporter, + subscriptionCookieManager: subscriptionCookieManager) self.syncPausedStateManager = syncPausedStateManager self.privacyProDataReporter = privacyProDataReporter self.homeTabManager = NewTabPageManager() @@ -232,6 +231,7 @@ class MainViewController: UIViewController { self.statisticsStore = statisticsStore self.subscriptionFeatureAvailability = subscriptionFeatureAvailability self.voiceSearchHelper = voiceSearchHelper + self.subscriptionCookieManager = subscriptionCookieManager super.init(nibName: nil, bundle: nil) @@ -486,7 +486,7 @@ class MainViewController: UIViewController { @objc private func keyboardWillHide() { - if homeController?.isDragging == true, keyboardShowing { + if newTabPageViewController?.isDragging == true, keyboardShowing { Pixel.fire(pixel: .addressBarGestureDismiss) } } @@ -681,7 +681,6 @@ class MainViewController: UIViewController { } self.menuBookmarksViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode self.favoritesViewModel.favoritesDisplayMode = self.appSettings.favoritesDisplayMode - self.homeController?.reloadFavorites() WidgetCenter.shared.reloadAllTimelines() } } @@ -695,9 +694,6 @@ class MainViewController: UIViewController { syncUpdatesCancellable = syncDataProviders.bookmarksAdapter.syncDidCompletePublisher .sink { [weak self] _ in self?.favoritesViewModel.reloadData() - DispatchQueue.main.async { - self?.homeController?.reloadFavorites() - } } } @@ -796,53 +792,34 @@ class MainViewController: UIViewController { } let newTabDaxDialogFactory = NewTabDaxDialogFactory(delegate: self, contextualOnboardingLogic: DaxDialogs.shared, onboardingPixelReporter: contextualOnboardingPixelReporter) - if homeTabManager.isNewTabPageSectionsEnabled { - let controller = NewTabPageViewController(tab: tabModel, - interactionModel: favoritesViewModel, - syncService: syncService, - syncBookmarksAdapter: syncDataProviders.bookmarksAdapter, - homePageMessagesConfiguration: homePageConfiguration, - privacyProDataReporting: privacyProDataReporter, - variantManager: variantManager, - newTabDialogFactory: newTabDaxDialogFactory, - newTabDialogTypeProvider: DaxDialogs.shared, - faviconLoader: faviconLoader) - - controller.delegate = self - controller.shortcutsDelegate = self - controller.chromeDelegate = self - - newTabPageViewController = controller - addToContentContainer(controller: controller) - viewCoordinator.logoContainer.isHidden = true - adjustNewTabPageSafeAreaInsets(for: appSettings.currentAddressBarPosition) - } else { - let homePageDependencies = HomePageDependencies(homePageConfiguration: homePageConfiguration, - model: tabModel, - favoritesViewModel: favoritesViewModel, - appSettings: appSettings, - syncService: syncService, - syncDataProviders: syncDataProviders, - privacyProDataReporter: privacyProDataReporter, - variantManager: variantManager, - newTabDialogFactory: newTabDaxDialogFactory, - newTabDialogTypeProvider: DaxDialogs.shared) - let controller = HomeViewController.loadFromStoryboard(homePageDependecies: homePageDependencies) - - controller.delegate = self - controller.chromeDelegate = self - homeViewController = controller - addToContentContainer(controller: controller) - } + let controller = NewTabPageViewController(tab: tabModel, + isNewTabPageCustomizationEnabled: homeTabManager.isNewTabPageSectionsEnabled, + interactionModel: favoritesViewModel, + syncService: syncService, + syncBookmarksAdapter: syncDataProviders.bookmarksAdapter, + homePageMessagesConfiguration: homePageConfiguration, + privacyProDataReporting: privacyProDataReporter, + variantManager: variantManager, + newTabDialogFactory: newTabDaxDialogFactory, + newTabDialogTypeProvider: DaxDialogs.shared, + faviconLoader: faviconLoader) + + controller.delegate = self + controller.shortcutsDelegate = self + controller.chromeDelegate = self + + newTabPageViewController = controller + addToContentContainer(controller: controller) + viewCoordinator.logoContainer.isHidden = true + adjustNewTabPageSafeAreaInsets(for: appSettings.currentAddressBarPosition) refreshControls() syncService.scheduler.requestSyncImmediately() } fileprivate func removeHomeScreen() { - homeController?.willMove(toParent: nil) - homeController?.dismiss() - homeViewController = nil + newTabPageViewController?.willMove(toParent: nil) + newTabPageViewController?.dismiss() newTabPageViewController = nil } @@ -1193,7 +1170,7 @@ class MainViewController: UIViewController { func refreshMenuButtonState() { let expectedState: MenuButton.State - if homeViewController != nil { + if !homeTabManager.isNewTabPageSectionsEnabled && newTabPageViewController != nil { expectedState = .bookmarksImage viewCoordinator.lastToolbarButton.accessibilityLabel = UserText.bookmarksButtonHint viewCoordinator.omniBar.menuButton.accessibilityLabel = UserText.bookmarksButtonHint @@ -1432,18 +1409,7 @@ class MainViewController: UIViewController { attachHomeScreen() tabsBarController?.refresh(tabsModel: tabManager.model) swipeTabsCoordinator?.refresh(tabsModel: tabManager.model, scrollToSelected: true) - homeController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) - } - - func animateLogoAppearance() { - viewCoordinator.logoContainer.alpha = 0 - viewCoordinator.logoContainer.transform = CGAffineTransform(scaleX: 0.5, y: 0.5) - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - UIView.animate(withDuration: 0.2) { - self.viewCoordinator.logoContainer.alpha = 1 - self.viewCoordinator.logoContainer.transform = CGAffineTransform(scaleX: 1.0, y: 1.0) - } - } + newTabPageViewController?.openedAsNewTab(allowingKeyboard: allowingKeyboard) } func updateFindInPage() { @@ -1753,7 +1719,7 @@ extension MainViewController: BrowserChromeDelegate { updateBlock() } } - + func setNavigationBarHidden(_ hidden: Bool) { if hidden { hideKeyboard() } @@ -1823,7 +1789,7 @@ extension MainViewController: OmniBarDelegate { func onOmniQueryUpdated(_ updatedQuery: String) { if updatedQuery.isEmpty { - if homeController != nil { + if newTabPageViewController != nil { hideSuggestionTray() } else { let didShow = tryToShowSuggestionTray(.favorites) @@ -1892,7 +1858,7 @@ extension MainViewController: OmniBarDelegate { let menuEntries: [BrowsingMenuEntry] let headerEntries: [BrowsingMenuEntry] - if isNewTabPageVisible { + if homeTabManager.isNewTabPageSectionsEnabled && newTabPageViewController != nil { menuEntries = tab.buildShortcutsMenu() headerEntries = [] } else { @@ -1933,7 +1899,7 @@ extension MainViewController: OmniBarDelegate { } func fireControllerAwarePixel(ntp: Pixel.Event, serp: Pixel.Event, website: Pixel.Event) { - if homeController != nil { + if newTabPageViewController != nil { Pixel.fire(pixel: ntp) } else if let currentTab { if currentTab.url?.isDuckDuckGoSearch == true { @@ -2005,7 +1971,7 @@ extension MainViewController: OmniBarDelegate { fireControllerAwarePixel(ntp: .addressBarClickOnNTP, serp: .addressBarClickOnSERP, website: .addressBarClickOnWebsite) } - guard homeController == nil else { return } + guard newTabPageViewController == nil else { return } if !skipSERPFlow, isSERPPresented, let query = omniBar.textField.text { tryToShowSuggestionTray(.autocomplete(query: query)) @@ -2022,10 +1988,10 @@ extension MainViewController: OmniBarDelegate { if !DaxDialogs.shared.shouldShowFireButtonPulse { ViewHighlighter.hideAll() } - guard let homeController = homeController else { + guard let newTabPageViewController = newTabPageViewController else { return selectQueryText } - homeController.launchNewSearch() + newTabPageViewController.launchNewSearch() return selectQueryText } @@ -2065,7 +2031,7 @@ extension MainViewController: FavoritesOverlayDelegate { func favoritesOverlay(_ overlay: FavoritesOverlay, didSelect favorite: BookmarkEntity) { guard let url = favorite.urlObject else { return } Pixel.fire(pixel: .favoriteLaunchedWebsite) - homeViewController?.chromeDelegate = nil + newTabPageViewController?.chromeDelegate = nil dismissOmniBar() Favicons.shared.loadFavicon(forDomain: url.host, intoCache: .fireproof, fromCache: .tabs) if url.isBookmarklet() { @@ -2088,7 +2054,7 @@ extension MainViewController: AutocompleteViewControllerDelegate { } func autocomplete(selectedSuggestion suggestion: Suggestion) { - homeViewController?.chromeDelegate = nil + newTabPageViewController?.chromeDelegate = nil dismissOmniBar() viewCoordinator.omniBar.cancel() switch suggestion { @@ -2113,7 +2079,7 @@ extension MainViewController: AutocompleteViewControllerDelegate { loadUrl(url) case .openTab(title: _, url: let url): - if homeViewController != nil, let tab = tabManager.model.currentTab { + if newTabPageViewController != nil, let tab = tabManager.model.currentTab { self.closeTab(tab) } loadUrlInNewTab(url, reuseExisting: true, inheritedAttribution: .noAttribution) @@ -2194,49 +2160,6 @@ extension MainViewController { } } -extension MainViewController: HomeControllerDelegate { - - func home(_ home: HomeViewController, didRequestQuery query: String) { - loadQueryInNewTab(query) - } - - func home(_ home: HomeViewController, didRequestUrl url: URL) { - handleRequestedURL(url) - } - - func home(_ home: HomeViewController, didRequestEdit favorite: BookmarkEntity) { - segueToEditBookmark(favorite) - } - - func home(_ home: HomeViewController, didRequestContentOverflow shouldOverflow: Bool) -> CGFloat { - allowContentUnderflow = shouldOverflow - return contentUnderflow - } - - func homeDidDeactivateOmniBar(home: HomeViewController) { - hideSuggestionTray() - dismissOmniBar() - } - - func showSettings(_ home: HomeViewController) { - segueToSettings() - } - - func home(_ home: HomeViewController, didRequestHideLogo hidden: Bool) { - viewCoordinator.logoContainer.isHidden = hidden - } - - func homeDidRequestLogoContainer(_ home: HomeViewController) -> UIView { - return viewCoordinator.logoContainer - } - - func home(_ home: HomeViewController, searchTransitionUpdated percent: CGFloat) { - viewCoordinator.statusBackground.alpha = percent - viewCoordinator.navigationBarContainer.alpha = percent - } - -} - extension MainViewController: NewTabPageControllerDelegate { func newTabPageDidOpenFavoriteURL(_ controller: NewTabPageViewController, url: URL) { handleRequestedURL(url) @@ -2512,17 +2435,19 @@ extension MainViewController: TabDelegate { extension MainViewController: TabSwitcherDelegate { + private func animateLogoAppearance() { + newTabPageViewController?.view.transform = CGAffineTransform().scaledBy(x: 0.5, y: 0.5) + newTabPageViewController?.view.alpha = 0.0 + UIView.animate(withDuration: 0.2, delay: 0.1, options: [.curveEaseInOut, .beginFromCurrentState]) { + self.newTabPageViewController?.view.transform = .identity + self.newTabPageViewController?.view.alpha = 1.0 + } + } + func tabSwitcherDidRequestNewTab(tabSwitcher: TabSwitcherViewController) { newTab() - if homeViewController != nil { + if newTabPageViewController != nil { animateLogoAppearance() - } else if newTabPageViewController != nil { - newTabPageViewController?.view.transform = CGAffineTransform().scaledBy(x: 0.5, y: 0.5) - newTabPageViewController?.view.alpha = 0.0 - UIView.animate(withDuration: 0.2, delay: 0.1, options: [.curveEaseInOut, .beginFromCurrentState]) { - self.newTabPageViewController?.view.transform = .identity - self.newTabPageViewController?.view.alpha = 1.0 - } } } @@ -2540,7 +2465,7 @@ extension MainViewController: TabSwitcherDelegate { // switcher is still presented. DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { tabSwitcher.dismiss(animated: true) { - self.homeController?.viewDidAppear(true) + self.newTabPageViewController?.viewDidAppear(true) } } } @@ -2746,7 +2671,7 @@ extension MainViewController: AutoClearWorker { // Ideally this should happen once data clearing has finished AND the animation is finished if showNextDaxDialog { - self.homeController?.showNextDaxDialog() + self.newTabPageViewController?.showNextDaxDialog() } else if KeyboardSettings().onNewTab { let showKeyboardAfterFireButton = DispatchWorkItem { self.enterSearch() @@ -2840,7 +2765,7 @@ extension MainViewController: OnboardingDelegate { markOnboardingSeen() controller.modalTransitionStyle = .crossDissolve controller.dismiss(animated: true) - homeController?.onboardingCompleted() + newTabPageViewController?.onboardingCompleted() } func markOnboardingSeen() { diff --git a/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift b/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift deleted file mode 100644 index 02c8f9023b..0000000000 --- a/DuckDuckGo/NavigationSearchHomeViewSectionRenderer.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// NavigationSearchHomeViewSectionRenderer.swift -// DuckDuckGo -// -// Copyright © 2018 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 NavigationSearchHomeViewSectionRenderer: HomeViewSectionRenderer { - - private let fixed: Bool - - init(fixed: Bool) { - self.fixed = fixed - } - - weak var controller: HomeViewController? - - func install(into controller: HomeViewController) { - self.controller = controller - - controller.collectionView.contentInset = UIEdgeInsets.zero - - controller.searchHeaderTransition = 1.0 - controller.disableContentUnderflow() - controller.chromeDelegate?.setNavigationBarHidden(false) - controller.collectionView.isScrollEnabled = !fixed - - if !fixed { - controller.hideLogo() - } - } - - func openedAsNewTab(allowingKeyboard: Bool) { - 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() - } - } - - func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return 0 - } - - func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - return collectionView.dequeueReusableCell(withReuseIdentifier: "space", for: indexPath) - } - - func collectionView(_ collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAt indexPath: IndexPath) -> CGSize { - return collectionView.frame.size - } - - func launchNewSearch() { - controller?.chromeDelegate?.omniBar.becomeFirstResponder() - } - -} diff --git a/DuckDuckGo/NetworkProtectionDebugViewController.swift b/DuckDuckGo/NetworkProtectionDebugViewController.swift index 9af0df4f9f..0c0c34568e 100644 --- a/DuckDuckGo/NetworkProtectionDebugViewController.swift +++ b/DuckDuckGo/NetworkProtectionDebugViewController.swift @@ -643,7 +643,10 @@ shouldShowVPNShortcut: \(vpnVisibility.shouldShowVPNShortcut() ? "YES" : "NO") @MainActor private func refreshMetadata() async { - let collector = DefaultVPNMetadataCollector(statusObserver: AppDependencyProvider.shared.connectionObserver) + let collector = DefaultVPNMetadataCollector( + statusObserver: AppDependencyProvider.shared.connectionObserver, + serverInfoObserver: AppDependencyProvider.shared.serverInfoObserver + ) self.vpnMetadata = await collector.collectMetadata() self.tableView.reloadData() } diff --git a/DuckDuckGo/NetworkProtectionStatusView.swift b/DuckDuckGo/NetworkProtectionStatusView.swift index f8508a2482..21e79aa654 100644 --- a/DuckDuckGo/NetworkProtectionStatusView.swift +++ b/DuckDuckGo/NetworkProtectionStatusView.swift @@ -19,11 +19,61 @@ import SwiftUI import NetworkProtection +import TipKit struct NetworkProtectionStatusView: View { + + static let defaultImageSize = CGSize(width: 32, height: 32) + @Environment(\.colorScheme) var colorScheme - @StateObject public var statusModel: NetworkProtectionStatusViewModel + @ObservedObject + public var statusModel: NetworkProtectionStatusViewModel + + // MARK: - Tips + + let geoswitchingTip: VPNGeoswitchingTip = { + let tip = VPNGeoswitchingTip() + + if #available(iOS 17.0, *) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNSnoozeTip.geolocationTipDismissedEvent.donate() + await VPNAddWidgetTip.geolocationTipDismissedEvent.donate() + } + } + } + } + } + + return tip + }() + + let snoozeTip: VPNSnoozeTip = { + let tip = VPNSnoozeTip() + + if #available(iOS 17.0, *) { + if tip.shouldDisplay { + Task { + for await status in tip.statusUpdates { + if case .invalidated = status { + await VPNAddWidgetTip.snoozeTipDismissedEvent.donate() + } + } + } + } + } + + return tip + }() + + let widgetTip: VPNAddWidgetTip = { + VPNAddWidgetTip() + }() + + // MARK: - View var body: some View { List { @@ -35,6 +85,7 @@ struct NetworkProtectionStatusView: View { } toggle() + locationDetails() if statusModel.isNetPEnabled && statusModel.hasServerInfo && !statusModel.isSnoozing { @@ -51,6 +102,9 @@ struct NetworkProtectionStatusView: View { .animation(.easeOut, value: statusModel.shouldShowError) }) .applyInsetGroupedListStyle() + .sheet(isPresented: $statusModel.showAddWidgetEducationView) { + widgetEducationSheet() + } } @ViewBuilder @@ -86,11 +140,27 @@ struct NetworkProtectionStatusView: View { .padding([.top, .bottom], 2) snooze() + } header: { header() } .increaseHeaderProminence() .listRowBackground(Color(designSystemColor: .surface)) + + Section { + if #available(iOS 17.0, *) { + widgetTipView() + .tipImageSize(Self.defaultImageSize) + .padding(.horizontal, 3) + } + + if #available(iOS 17.0, *) { + snoozeTipView() + .tipImageSize(Self.defaultImageSize) + .padding(.horizontal, 3) + } + } + .listRowBackground(Color(designSystemColor: .surface)) } @ViewBuilder @@ -151,8 +221,8 @@ struct NetworkProtectionStatusView: View { @ViewBuilder private func locationDetails() -> some View { - if !statusModel.isSnoozing, let location = statusModel.location { - Section { + Section { + if !statusModel.isSnoozing, let location = statusModel.location { var locationAttributedString: AttributedString { var attributedString = AttributedString( statusModel.preferredLocation.isNearest ? "\(location) \(UserText.netPVPNLocationNearest)" : location @@ -164,16 +234,10 @@ struct NetworkProtectionStatusView: View { return attributedString } - NavigationLink(destination: NetworkProtectionVPNLocationView()) { + NavigationLink(destination: locationView()) { NetworkProtectionLocationItemView(title: locationAttributedString, imageName: nil) } - } header: { - Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) - .foregroundColor(.init(designSystemColor: .textSecondary)) - } - .listRowBackground(Color(designSystemColor: .surface)) - } else { - Section { + } else { let imageName = statusModel.preferredLocation.isNearest ? "VPNLocation" : nil var nearestLocationAttributedString: AttributedString { var attributedString = AttributedString(statusModel.preferredLocation.title) @@ -181,15 +245,32 @@ struct NetworkProtectionStatusView: View { return attributedString } - NavigationLink(destination: NetworkProtectionVPNLocationView()) { + NavigationLink(destination: locationView()) { NetworkProtectionLocationItemView(title: nearestLocationAttributedString, imageName: imageName) } - } header: { - Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) - .foregroundColor(.init(designSystemColor: .textSecondary)) } - .listRowBackground(Color(designSystemColor: .surface)) + } header: { + Text(statusModel.isNetPEnabled ? UserText.vpnLocationConnected : UserText.vpnLocationSelected) + .foregroundColor(.init(designSystemColor: .textSecondary)) + } + .listRowBackground(Color(designSystemColor: .surface)) + + Section { + if #available(iOS 17.0, *) { + geoswitchingTipView() + .tipImageSize(Self.defaultImageSize) + .padding(.horizontal, 3) + } } + .listRowBackground(Color(designSystemColor: .surface)) + } + + @ViewBuilder + private func locationView() -> some View { + NetworkProtectionVPNLocationView() + .onAppear { + statusModel.handleUserOpenedVPNLocations() + } } @ViewBuilder @@ -267,6 +348,61 @@ struct NetworkProtectionStatusView: View { isAnimating: $statusModel.isNetPEnabled ) } + + // MARK: - Tips + + @available(iOS 17.0, *) + @ViewBuilder + private func geoswitchingTipView() -> some View { + if statusModel.canShowTips { + + TipView(geoswitchingTip) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + @available(iOS 17.0, *) + @ViewBuilder + private func snoozeTipView() -> some View { + if statusModel.canShowTips, + statusModel.hasServerInfo { + + TipView(snoozeTip, action: statusModel.snoozeActionHandler(action:)) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + @available(iOS 17.0, *) + @ViewBuilder + private func widgetTipView() -> some View { + if statusModel.canShowTips, + !statusModel.isNetPEnabled && !statusModel.isSnoozing { + + TipView(widgetTip, action: statusModel.widgetActionHandler(action:)) + .removeGroupedListStyleInsets() + .tipCornerRadius(0) + .tipBackground(Color(designSystemColor: .surface)) + } + } + + // MARK: - Sheets + + private func widgetEducationSheet() -> some View { + NavigationView { + WidgetEducationView() + .toolbar { + ToolbarItem(placement: .navigationBarTrailing) { + Button(UserText.navigationTitleDone) { + statusModel.showAddWidgetEducationView = false + } + } + } + } + } } private struct NetworkProtectionErrorView: View { diff --git a/DuckDuckGo/NetworkProtectionStatusViewModel.swift b/DuckDuckGo/NetworkProtectionStatusViewModel.swift index 7e7e5dc51f..becccb79aa 100644 --- a/DuckDuckGo/NetworkProtectionStatusViewModel.swift +++ b/DuckDuckGo/NetworkProtectionStatusViewModel.swift @@ -24,6 +24,7 @@ import WidgetKit import BrowserServicesKit import Core import Subscription +import TipKit struct NetworkProtectionLocationStatusModel { enum LocationIcon { @@ -94,12 +95,24 @@ final class NetworkProtectionStatusViewModel: ObservableObject { return formatter }() + private let featureFlagger = AppDependencyProvider.shared.featureFlagger private let tunnelController: (TunnelController & TunnelSessionProvider) private let statusObserver: ConnectionStatusObserver private let serverInfoObserver: ConnectionServerInfoObserver private let errorObserver: ConnectionErrorObserver private var cancellables: Set = [] + // MARK: - Tips + + var canShowTips: Bool { + featureFlagger.isFeatureOn(.networkProtectionUserTips) + } + + /// Whether the "Add Widget" education sheet should be presented to the user. + /// + @Published + var showAddWidgetEducationView: Bool = false + // MARK: Error struct ErrorItem { @@ -121,7 +134,19 @@ final class NetworkProtectionStatusViewModel: ObservableObject { // MARK: Toggle Item - @Published public var isNetPEnabled = false + @Published public var isNetPEnabled = false { + didSet { + if #available(iOS 17.0, *) { + if isNetPEnabled { + VPNGeoswitchingTip.donateVPNConnectedEvent() + } + + VPNSnoozeTip.vpnEnabled = isNetPEnabled + VPNAddWidgetTip.vpnEnabled = isNetPEnabled + } + } + } + @Published public var isSnoozing = false { didSet { snoozeRequestPending = false @@ -452,6 +477,10 @@ final class NetworkProtectionStatusViewModel: ObservableObject { return } + if #available(iOS 17.0, *) { + VPNSnoozeTip().invalidate(reason: .actionPerformed) + } + let defaultDuration: TimeInterval = .minutes(20) snoozeRequestPending = true try? await activeSession.sendProviderMessage(.startSnooze(defaultDuration)) @@ -546,6 +575,35 @@ final class NetworkProtectionStatusViewModel: ObservableObject { self.downloadTotal = nil } + // MARK: - UI Events handling + + @available(iOS 17.0, *) + func snoozeActionHandler(action: Tips.Action) { + if action.id == VPNSnoozeTip.ActionIdentifiers.learnMore.rawValue { + let url = URL(string: "https://duckduckgo.com/duckduckgo-help-pages/privacy-pro/vpn/troubleshooting/")! + UIApplication.shared.open(url, options: [:], completionHandler: nil) + } + } + + @available(iOS 17.0, *) + @MainActor + func widgetActionHandler(action: Tips.Action) { + if action.id == VPNAddWidgetTip.ActionIdentifiers.addWidget.rawValue { + showAddWidgetEducationView = true + + VPNAddWidgetTip().invalidate(reason: .actionPerformed) + } + } + + /// The user opened the VPN locations view + /// + func handleUserOpenedVPNLocations() { + if #available(iOS 17.0, *) { + Task { @MainActor in + VPNGeoswitchingTip().invalidate(reason: .actionPerformed) + } + } + } } private extension ConnectionStatus { diff --git a/DuckDuckGo/NewTabPage.swift b/DuckDuckGo/NewTabPage.swift index 7be2446e29..eb382f9b5e 100644 --- a/DuckDuckGo/NewTabPage.swift +++ b/DuckDuckGo/NewTabPage.swift @@ -21,8 +21,7 @@ import UIKit protocol NewTabPage: UIViewController { - var isDragging: Bool { get } // TODO: Mariusz, check if needed in both - func reloadFavorites() // TODO: Mariusz: check if needed with reactive approach + var isDragging: Bool { get } func launchNewSearch() func openedAsNewTab(allowingKeyboard: Bool) diff --git a/DuckDuckGo/NewTabPageView.swift b/DuckDuckGo/NewTabPageView.swift index aaed282ca5..219ddd9c27 100644 --- a/DuckDuckGo/NewTabPageView.swift +++ b/DuckDuckGo/NewTabPageView.swift @@ -34,6 +34,8 @@ struct NewTabPageView: View { @State private var customizeButtonShowedInline = false @State private var isAddingFavorite: Bool = false + @State var isDragging: Bool = false + init(viewModel: NewTabPageViewModel, messagesModel: NewTabPageMessagesModel, favoritesViewModel: FavoritesViewModel, @@ -71,6 +73,15 @@ struct NewTabPageView: View { sectionsSettingsModel: sectionsSettingsModel) } }) + .simultaneousGesture( + DragGesture() + .onChanged({ value in + if value.translation.height > 0 { + viewModel.beginDragging() + } + }) + .onEnded({ _ in viewModel.endDragging() }) + ) } } diff --git a/DuckDuckGo/NewTabPageViewController.swift b/DuckDuckGo/NewTabPageViewController.swift index ee3d0bb4a0..0f24599de6 100644 --- a/DuckDuckGo/NewTabPageViewController.swift +++ b/DuckDuckGo/NewTabPageViewController.swift @@ -23,7 +23,7 @@ import Bookmarks import BrowserServicesKit import Core -final class NewTabPageViewController: UIHostingController, NewTabPage { +final class NewTabPageViewController: UIHostingController, NewTabPage { private let syncService: DDGSyncing private let syncBookmarksAdapter: SyncBookmarksAdapter @@ -43,7 +43,15 @@ final class NewTabPageViewController: UIHostingController, NewTa private var hostingController: UIHostingController? + private weak var daxDialogViewController: DaxDialogViewController? + private var daxDialogHeightConstraint: NSLayoutConstraint? + + var isDaxDialogVisible: Bool { + daxDialogViewController?.view.isHidden == false + } + init(tab: Tab, + isNewTabPageCustomizationEnabled: Bool, interactionModel: FavoritesListInteracting, syncService: DDGSyncing, syncBookmarksAdapter: SyncBookmarksAdapter, @@ -64,23 +72,35 @@ final class NewTabPageViewController: UIHostingController, NewTa newTabPageViewModel = NewTabPageViewModel() shortcutsSettingsModel = NewTabPageShortcutsSettingsModel() sectionsSettingsModel = NewTabPageSectionsSettingsModel() - favoritesModel = FavoritesViewModel(favoriteDataSource: FavoritesListInteractingAdapter(favoritesListInteracting: interactionModel), faviconLoader: faviconLoader) + favoritesModel = FavoritesViewModel(isNewTabPageCustomizationEnabled: isNewTabPageCustomizationEnabled, + favoriteDataSource: FavoritesListInteractingAdapter(favoritesListInteracting: interactionModel), + faviconLoader: faviconLoader) shortcutsModel = ShortcutsModel() messagesModel = NewTabPageMessagesModel(homePageMessagesConfiguration: homePageMessagesConfiguration, privacyProDataReporter: privacyProDataReporting) - let newTabPageView = NewTabPageView(viewModel: newTabPageViewModel, - messagesModel: messagesModel, - favoritesViewModel: favoritesModel, - shortcutsModel: shortcutsModel, - shortcutsSettingsModel: shortcutsSettingsModel, - sectionsSettingsModel: sectionsSettingsModel) - - super.init(rootView: newTabPageView) + if isNewTabPageCustomizationEnabled { + super.init(rootView: AnyView(NewTabPageView(viewModel: self.newTabPageViewModel, + messagesModel: self.messagesModel, + favoritesViewModel: self.favoritesModel, + shortcutsModel: self.shortcutsModel, + shortcutsSettingsModel: self.shortcutsSettingsModel, + sectionsSettingsModel: self.sectionsSettingsModel))) + } else { + super.init(rootView: AnyView(SimpleNewTabPageView(viewModel: self.newTabPageViewModel, + messagesModel: self.messagesModel, + favoritesViewModel: self.favoritesModel))) + } assignFavoriteModelActions() assignShorcutsModelActions() } + override func viewDidLoad() { + super.viewDidLoad() + + setUpDaxDialog() + } + override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) @@ -90,6 +110,34 @@ final class NewTabPageViewController: UIHostingController, NewTa Pixel.fire(pixel: .homeScreenShown) sendDailyDisplayPixel() + + view.backgroundColor = UIColor(designSystemColor: .background) + } + + private func setUpDaxDialog() { + let daxDialogController = DaxDialogViewController.loadFromStoryboard() + guard let dialogView = daxDialogController.view else { return } + + self.addChild(daxDialogController) + self.view.addSubview(dialogView) + + dialogView.translatesAutoresizingMaskIntoConstraints = false + dialogView.isHidden = true + + let widthConstraint = dialogView.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor, multiplier: 1) + widthConstraint.priority = .defaultHigh + let heightConstraint = dialogView.heightAnchor.constraint(equalToConstant: 250) + daxDialogHeightConstraint = heightConstraint + NSLayoutConstraint.activate([ + dialogView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 44.0), + dialogView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + dialogView.widthAnchor.constraint(lessThanOrEqualToConstant: 375), + heightConstraint, + widthConstraint + ]) + + daxDialogController.didMove(toParent: self) + daxDialogViewController = daxDialogController } // MARK: - Private @@ -140,7 +188,7 @@ final class NewTabPageViewController: UIHostingController, NewTa // MARK: - NewTabPage - let isDragging: Bool = false + var isDragging: Bool { newTabPageViewModel.isDragging } weak var chromeDelegate: BrowserChromeDelegate? weak var delegate: NewTabPageControllerDelegate? @@ -151,12 +199,18 @@ final class NewTabPageViewController: UIHostingController, NewTa } func openedAsNewTab(allowingKeyboard: Bool) { - guard allowingKeyboard && KeyboardSettings().onNewTab else { return } + if allowingKeyboard && KeyboardSettings().onNewTab { - // The omnibar is inside a collection view so this needs a chance to do its thing - // which might also be async. Not great. - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.launchNewSearch() + // The omnibar is inside a collection view so this needs a chance to do its thing + // which might also be async. Not great. + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.launchNewSearch() + } + } + + if !variantManager.isContextualDaxDialogsEnabled { + // In the new onboarding this gets called twice (viewDidAppear in Tab) which then reset the spec to nil. + presentNextDaxDialog() } } @@ -165,7 +219,7 @@ final class NewTabPageViewController: UIHostingController, NewTa } func showNextDaxDialog() { - showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory) + presentNextDaxDialog() } func onboardingCompleted() { @@ -181,6 +235,8 @@ final class NewTabPageViewController: UIHostingController, NewTa private func presentNextDaxDialog() { if variantManager.isContextualDaxDialogsEnabled { showNextDaxDialogNew(dialogProvider: newTabDialogTypeProvider, factory: newTabDialogFactory) + } else { + showNextDaxDialog(dialogProvider: newTabDialogTypeProvider) } } @@ -218,6 +274,37 @@ extension NewTabPageViewController: HomeScreenTransitionSource { extension NewTabPageViewController { + func showNextDaxDialog(dialogProvider: NewTabDialogSpecProvider) { + guard let spec = dialogProvider.nextHomeScreenMessage() else { return } + guard !isDaxDialogVisible else { return } + guard let daxDialogViewController = daxDialogViewController else { return } + + newTabPageViewModel.startOnboarding() + + daxDialogViewController.view.isHidden = false + daxDialogViewController.view.alpha = 0.0 + + daxDialogViewController.loadViewIfNeeded() + daxDialogViewController.message = spec.message + daxDialogViewController.accessibleMessage = spec.accessibilityLabel + + if spec == .initial { + UniquePixel.fire(pixel: .onboardingContextualTryVisitSiteUnique, includedParameters: [.appVersion, .atb]) + } + + view.addGestureRecognizer(daxDialogViewController.tapToCompleteGestureRecognizer) + + daxDialogHeightConstraint?.constant = daxDialogViewController.calculateHeight() + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + UIView.animate(withDuration: 0.4, animations: { + daxDialogViewController.view.alpha = 1.0 + }, completion: { _ in + daxDialogViewController.start() + }) + } + } + func showNextDaxDialogNew(dialogProvider: NewTabDialogSpecProvider, factory: any NewTabDaxDialogProvider) { dismissHostingController(didFinishNTPOnboarding: false) diff --git a/DuckDuckGo/NewTabPageViewModel.swift b/DuckDuckGo/NewTabPageViewModel.swift index b53a7625ea..6cb387b402 100644 --- a/DuckDuckGo/NewTabPageViewModel.swift +++ b/DuckDuckGo/NewTabPageViewModel.swift @@ -26,6 +26,8 @@ final class NewTabPageViewModel: ObservableObject { @Published private(set) var isOnboarding: Bool @Published var isShowingSettings: Bool + private(set) var isDragging: Bool = false + private var introDataStorage: NewTabPageIntroDataStoring private let pixelFiring: PixelFiring.Type @@ -67,4 +69,12 @@ final class NewTabPageViewModel: ObservableObject { func finishOnboarding() { isOnboarding = false } + + func beginDragging() { + isDragging = true + } + + func endDragging() { + isDragging = false + } } diff --git a/DuckDuckGo/OmniBar.swift b/DuckDuckGo/OmniBar.swift index d12d00d8e9..c2b6877be7 100644 --- a/DuckDuckGo/OmniBar.swift +++ b/DuckDuckGo/OmniBar.swift @@ -263,6 +263,7 @@ class OmniBar: UIView { let icon = PrivacyIconLogic.privacyIcon(for: url) privacyInfoContainer.privacyIcon.updateIcon(icon) + customIconView.isHidden = true } public func updatePrivacyIcon(for privacyInfo: PrivacyInfo?) { @@ -275,11 +276,11 @@ class OmniBar: UIView { showCustomIcon(icon: .duckPlayer) return } - - customIconView.isHidden = true + privacyInfoContainer.privacyIcon.isHidden = privacyInfo.isSpecialErrorPageVisible let icon = PrivacyIconLogic.privacyIcon(for: privacyInfo) privacyInfoContainer.privacyIcon.updateIcon(icon) + customIconView.isHidden = true } // Support static custom icons, for things like internal pages, for example @@ -384,8 +385,8 @@ class OmniBar: UIView { searchStackContainer.setCustomSpacing(13, after: voiceSearchButton) } - UIView.animate(withDuration: 0.0) { - self.layoutIfNeeded() + UIView.animate(withDuration: 0.0) { [weak self] in + self?.layoutIfNeeded() } } diff --git a/DuckDuckGo/OnboardingDebugView.swift b/DuckDuckGo/OnboardingDebugView.swift index bc545de5b9..88976baa81 100644 --- a/DuckDuckGo/OnboardingDebugView.swift +++ b/DuckDuckGo/OnboardingDebugView.swift @@ -33,7 +33,7 @@ struct OnboardingDebugView: View { List { Section { Toggle( - isOn: $viewModel.isLocalFlagEnabled, + isOn: $viewModel.isOnboardingHighlightsLocalFlagEnabled, label: { Text(verbatim: "Onboarding Highlights local setting enabled") } @@ -44,9 +44,22 @@ struct OnboardingDebugView: View { Text(verbatim: "Requires internal user flag set to have an effect.") } + Section { + Toggle( + isOn: $viewModel.isOnboardingAddToDockLocalFlagEnabled, + label: { + Text(verbatim: "Onboarding Add to Dock local setting enabled") + } + ) + } header: { + Text(verbatim: "Onboarding Add to Dock settings") + } footer: { + Text(verbatim: "Requires internal user flag set to have an effect.") + } + Section { Button(action: newOnboardingIntroStartAction, label: { - let onboardingType = viewModel.isLocalFlagEnabled ? "Highlights" : "" + let onboardingType = viewModel.isOnboardingHighlightsLocalFlagEnabled ? "Highlights" : "" Text(verbatim: "Preview New Onboarding Intro \(onboardingType)") }) } @@ -55,17 +68,24 @@ struct OnboardingDebugView: View { } final class OnboardingDebugViewModel: ObservableObject { - @Published var isLocalFlagEnabled: Bool { + @Published var isOnboardingHighlightsLocalFlagEnabled: Bool { + didSet { + manager.isOnboardingHighlightsLocalFlagEnabled = isOnboardingHighlightsLocalFlagEnabled + } + } + + @Published var isOnboardingAddToDockLocalFlagEnabled: Bool { didSet { - manager.isLocalFlagEnabled = isLocalFlagEnabled + manager.isAddToDockLocalFlagEnabled = isOnboardingAddToDockLocalFlagEnabled } } - private let manager: OnboardingHighlightsDebugging + private let manager: OnboardingHighlightsDebugging & OnboardingAddToDockDebugging - init(manager: OnboardingHighlightsDebugging = OnboardingManager()) { + init(manager: OnboardingHighlightsDebugging & OnboardingAddToDockDebugging = OnboardingManager()) { self.manager = manager - isLocalFlagEnabled = manager.isLocalFlagEnabled + isOnboardingHighlightsLocalFlagEnabled = manager.isOnboardingHighlightsLocalFlagEnabled + isOnboardingAddToDockLocalFlagEnabled = manager.isAddToDockLocalFlagEnabled } } diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockPromoView.swift b/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockPromoView.swift new file mode 100644 index 0000000000..3d69df3eba --- /dev/null +++ b/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockPromoView.swift @@ -0,0 +1,50 @@ +// +// AddToDockPromoView.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 Lottie + +struct AddToDockPromoView: View { + private static let appIconFillKeyPath = "**.Backdrop.Fill 1.Color" + + private var model = AddToDockPromoViewModel() + + @State private var isAnimating = false + + var body: some View { + LottieView( + lottieFile: "add-to-dock-promo", + isAnimating: $isAnimating, + animationImageProvider: model, + valueProvider: .init( + provider: ColorValueProvider(model.color), + keypath: AnimationKeypath(keypath: Self.appIconFillKeyPath) + ) + ) + .onFirstAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + isAnimating = true + } + } + } +} + +#Preview { + AddToDockPromoView() +} diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockPromoViewModel.swift b/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockPromoViewModel.swift new file mode 100644 index 0000000000..1dd2f5c941 --- /dev/null +++ b/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockPromoViewModel.swift @@ -0,0 +1,66 @@ +// +// AddToDockPromoViewModel.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 Lottie + +final class AddToDockPromoViewModel { + private static let imageName = "img_0.png" + + private let appIconManager: AppIconProviding + + var color: LottieColor { + LottieColor(icon: appIconManager.appIcon) + } + + init(appIconManager: AppIconProviding = AppIconManager.shared) { + self.appIconManager = appIconManager + } +} + +extension AddToDockPromoViewModel: AnimationImageProvider { + + func imageForAsset(asset: Lottie.ImageAsset) -> CGImage? { + asset.name == Self.imageName ? UIImage(resource: .addToDockGradient).cgImage : nil + } + +} + +// MARK: - Helpers + +private extension LottieColor { + + init(icon: AppIcon) { + switch icon { + case .red: + self = LottieColor(r: 0.87, g: 0.34, b: 0.2, a: 1.0) + case .yellow: + self = LottieColor(r: 0.89, g: 0.64, b: 0.07, a: 1.0) + case .green: + self = LottieColor(r: 0.22, g: 0.62, b: 0.16, a: 1.0) + case .blue: + self = LottieColor(r: 0.22, g: 0.41, b: 0.94, a: 1.0) + case .purple: + self = LottieColor(r: 0.42, g: 0.31, b: 0.73, a: 1.0) + case .black: + self = LottieColor(r: 0, g: 0, b: 0, a: 1.0) + } + } + +} diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockTutorialView.swift b/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockTutorialView.swift new file mode 100644 index 0000000000..e13ed55c9f --- /dev/null +++ b/DuckDuckGo/OnboardingExperiment/AddToDock/AddToDockTutorialView.swift @@ -0,0 +1,124 @@ +// +// AddToDockTutorialView.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 Onboarding +import DuckUI + +private struct VideoPlayerFramePreferenceKey: PreferenceKey { + static var defaultValue: CGRect = .zero + static func reduce(value: inout CGRect, nextValue: () -> CGRect) {} +} + +struct AddToDockTutorialView: View { + private static let videoSize = CGSize(width: 898.0, height: 680.0) + private static let videoURL = Bundle.main.url(forResource: "add-to-dock-demo", withExtension: "mp4")! + + private let title: String + private let message: String + private let action: () -> Void + + @State private var animateTitle = true + @State private var animateMessage = false + @State private var showContent = false + @State private var isPlaying: Bool = false + @State private var videoPlayerWidth: CGFloat = 0.0 + @StateObject private var videoPlayerModel = VideoPlayerViewModel(url: Self.videoURL, loopVideo: true) + + init( + title: String, + message: String, + action: @escaping () -> Void + ) { + self.title = title + self.message = message + self.action = action + } + + var body: some View { + VStack(spacing: 24.0) { + AnimatableTypingText(title, startAnimating: $animateTitle) { + withAnimation { + animateMessage = true + } + } + .foregroundColor(.primary) + .font(Font.system(size: 20, weight: .bold)) + + AnimatableTypingText(message, startAnimating: $animateMessage) { + withAnimation { + showContent = true + } + } + .foregroundColor(.primary) + .font(Font.system(size: 16)) + + videoPlayer + .visibility(showContent ? .visible : .invisible) + .onChange(of: showContent) { newValue in + if newValue { + // Need to delay playing a video. If calling isPlaying too early the video won't play. + DispatchQueue.main.async { + isPlaying = true + } + } + } + + Button(action: action) { + Text(UserText.AddToDockOnboarding.Buttons.dismiss) + } + .buttonStyle(PrimaryButtonStyle()) + .visibility(showContent ? .visible : .invisible) + } + .onFrameUpdate(in: .global, using: VideoPlayerFramePreferenceKey.self) { rect in + videoPlayerWidth = rect.width + } + } + + private var videoPlayer: some View { + // Calculate the height of the video based on the width it takes maintaining its aspect ratio + let heightRatio = videoPlayerWidth * (Self.videoSize.height / Self.videoSize.width) + return VideoPlayerView(model: videoPlayerModel, isPlaying: $isPlaying) + .frame(width: videoPlayerWidth, height: heightRatio) + } + +} + +// MARK: - Preview + +struct AddToDockTutorial_Previews: PreviewProvider { + + struct AddToDockPreview: View { + + var body: some View { + AddToDockTutorialView( + title: UserText.AddToDockOnboarding.Tutorial.title, + message: UserText.AddToDockOnboarding.Tutorial.message, + action: {} + ) + .padding() + } + } + + static var previews: some View { + AddToDockPreview() + .preferredColorScheme(.light) + } + +} diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/Resources/add-to-dock-demo.mp4 b/DuckDuckGo/OnboardingExperiment/AddToDock/Resources/add-to-dock-demo.mp4 new file mode 100644 index 0000000000..bde8ebbfd8 Binary files /dev/null and b/DuckDuckGo/OnboardingExperiment/AddToDock/Resources/add-to-dock-demo.mp4 differ diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/Resources/add-to-dock-promo.json b/DuckDuckGo/OnboardingExperiment/AddToDock/Resources/add-to-dock-promo.json new file mode 100644 index 0000000000..374632bffd --- /dev/null +++ b/DuckDuckGo/OnboardingExperiment/AddToDock/Resources/add-to-dock-promo.json @@ -0,0 +1,12131 @@ +{ + "v": "5.9.0", + "fr": 60, + "ip": 0, + "op": 120, + "w": 155, + "h": 52, + "nm": "Add-to-Dock Promo", + "ddd": 0, + "assets": + [ + { + "id": "image_0", + "w": 588, + "h": 176, + "u": "images/", + "p": "img_0.png", + "e": 0 + }, + { + "id": "comp_0", + "nm": "Pre-comp 1", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 0, + "nm": "Stars - Bottom Right", + "refId": "comp_1", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 78, + 78, + 0 + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 16, + 16, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + -100, + -100, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 32, + "h": 32, + "ip": 20, + "op": 475, + "st": 20, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 0, + "nm": "Stars - Top Left", + "refId": "comp_1", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 26, + 26, + 0 + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 16, + 16, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 100, + 100, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 32, + "h": 32, + "ip": 20, + "op": 475, + "st": 20, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 0, + "nm": "DuckDuckGo Icon", + "refId": "comp_2", + "sr": 1, + "ks": + { + "o": + { + "a": 1, + "k": + [ + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 20, + "s": + [ + 0 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 35, + "s": + [ + 100 + ] + }, + { + "t": 42, + "s": + [ + 100 + ] + } + ], + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 52, + 52, + 0 + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 28, + 28, + 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": 20, + "s": + [ + 10, + 10, + 100 + ] + }, + { + "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": 35, + "s": + [ + 120, + 120, + 100 + ] + }, + { + "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": 42, + "s": + [ + 80, + 80, + 100 + ] + }, + { + "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": 47, + "s": + [ + 106, + 106, + 100 + ] + }, + { + "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": 52, + "s": + [ + 94, + 94, + 100 + ] + }, + { + "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": 58, + "s": + [ + 102, + 102, + 100 + ] + }, + { + "t": 65, + "s": + [ + 100, + 100, + 100 + ] + } + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 56, + "h": 56, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 0, + "nm": "Mail Icon", + "refId": "comp_3", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "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": 20, + "s": + [ + 155, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 30, + "s": + [ + 196, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 35, + "s": + [ + 182, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 39, + "s": + [ + 191, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 45, + "s": + [ + 191, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 50, + "s": + [ + 188, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "t": 60, + "s": + [ + 191, + 52, + 0 + ] + } + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 28, + 28, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 100, + 100, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 56, + "h": 56, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 0, + "nm": "Music Icon", + "refId": "comp_4", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "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": 20, + "s": + [ + 225, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 30, + "s": + [ + 266, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 35, + "s": + [ + 252, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 39, + "s": + [ + 261, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 45, + "s": + [ + 261, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 50, + "s": + [ + 258, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "t": 60, + "s": + [ + 261, + 52, + 0 + ] + } + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 28, + 28, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 100, + 100, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 56, + "h": 56, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 0, + "nm": "Phone Icon", + "refId": "comp_5", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "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": 20, + "s": + [ + 85, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 30, + "s": + [ + 126, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 35, + "s": + [ + 112, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 39, + "s": + [ + 121, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 45, + "s": + [ + 121, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 50, + "s": + [ + 118, + 52, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "t": 60, + "s": + [ + 121, + 52, + 0 + ] + } + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 28, + 28, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 100, + 100, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 56, + "h": 56, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 0, + "nm": "Background-Vector", + "refId": "comp_6", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 155, + 52, + 0 + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 155, + 52, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 100, + 100, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 310, + "h": 104, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_1", + "nm": "Stars 01", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Star", + "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 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 16, + "s": + [ + 80 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 17, + "s": + [ + 100 + ] + }, + { + "t": 29, + "s": + [ + 0 + ] + } + ], + "ix": 11 + }, + "r": + { + "a": 1, + "k": + [ + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 0, + "s": + [ + 45 + ] + }, + { + "t": 29, + "s": + [ + -124.047 + ] + } + ], + "ix": 10 + }, + "p": + { + "a": 1, + "k": + [ + { + "i": + { + "x": 0.833, + "y": 0.833 + }, + "o": + { + "x": 0.167, + "y": 0.167 + }, + "t": 0, + "s": + [ + 26, + 25.083, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "t": 29, + "s": + [ + 5.408, + 5.61, + 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": 0, + "s": + [ + -63.663, + 63.663, + 100 + ] + }, + { + "t": 29, + "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.429, + -0.086 + ], + [ + 0, + 0 + ], + [ + 0, + -0.6 + ], + [ + 0.6, + -0.171 + ], + [ + 0, + 0 + ], + [ + 0.086, + -0.429 + ], + [ + 0, + 0 + ], + [ + 0.6, + 0 + ], + [ + 0.171, + 0.6 + ], + [ + 0, + 0 + ], + [ + 0.429, + 0.086 + ], + [ + 0, + 0 + ], + [ + 0, + 0.6 + ], + [ + -0.6, + 0.171 + ], + [ + 0, + 0 + ], + [ + -0.086, + 0.429 + ], + [ + 0, + 0 + ], + [ + -0.6, + 0 + ], + [ + -0.171, + -0.6 + ], + [ + 0, + 0 + ] + ], + "o": + [ + [ + 0, + 0 + ], + [ + 0.514, + 0.086 + ], + [ + 0, + 0.514 + ], + [ + 0, + 0 + ], + [ + -0.429, + 0.086 + ], + [ + 0, + 0 + ], + [ + -0.086, + 0.514 + ], + [ + -0.514, + 0 + ], + [ + 0, + 0 + ], + [ + -0.086, + -0.429 + ], + [ + 0, + 0 + ], + [ + -0.514, + -0.086 + ], + [ + 0, + -0.514 + ], + [ + 0, + 0 + ], + [ + 0.429, + -0.086 + ], + [ + 0, + 0 + ], + [ + 0.086, + -0.514 + ], + [ + 0.514, + 0 + ], + [ + 0, + 0 + ], + [ + 0.086, + 0.429 + ] + ], + "v": + [ + [ + 2.743, + -1.8 + ], + [ + 5.057, + -1.2 + ], + [ + 6, + 0 + ], + [ + 5.057, + 1.2 + ], + [ + 2.743, + 1.8 + ], + [ + 1.8, + 2.743 + ], + [ + 1.2, + 5.057 + ], + [ + 0, + 6 + ], + [ + -1.2, + 5.057 + ], + [ + -1.8, + 2.743 + ], + [ + -2.743, + 1.8 + ], + [ + -5.057, + 1.2 + ], + [ + -6, + 0 + ], + [ + -5.057, + -1.2 + ], + [ + -2.743, + -1.8 + ], + [ + -1.8, + -2.743 + ], + [ + -1.2, + -5.057 + ], + [ + 0, + -6 + ], + [ + 1.2, + -5.057 + ], + [ + 1.8, + -2.743 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.22265625, + 0.411768317223, + 0.9375, + 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": "Star", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Dot", + "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 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 6, + "s": + [ + 80 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 16, + "s": + [ + 80 + ] + }, + { + "t": 29, + "s": + [ + 0 + ] + } + ], + "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": 0, + "s": + [ + 26.472, + 20.361, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "t": 29, + "s": + [ + 21.062, + 2.75, + 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": 0, + "s": + [ + 68.889, + 68.889, + 100 + ] + }, + { + "t": 29, + "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, + -1.375 + ], + [ + -1.375, + 0 + ], + [ + 0, + 1.375 + ], + [ + 1.375, + 0 + ] + ], + "o": + [ + [ + 0, + 1.375 + ], + [ + 1.375, + 0 + ], + [ + 0, + -1.375 + ], + [ + -1.375, + 0 + ] + ], + "v": + [ + [ + -2.5, + 0 + ], + [ + 0, + 2.5 + ], + [ + 2.5, + 0 + ], + [ + 0, + -2.5 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.22265625, + 0.411768317223, + 0.9375, + 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": "Dot", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Dot", + "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 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 6, + "s": + [ + 64 + ] + }, + { + "i": + { + "x": + [ + 0.833 + ], + "y": + [ + 0.833 + ] + }, + "o": + { + "x": + [ + 0.167 + ], + "y": + [ + 0.167 + ] + }, + "t": 16, + "s": + [ + 64 + ] + }, + { + "t": 29, + "s": + [ + 0 + ] + } + ], + "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": 0, + "s": + [ + 18.893, + 28.857, + 0 + ], + "to": + [ + 0, + 0, + 0 + ], + "ti": + [ + 0, + 0, + 0 + ] + }, + { + "t": 29, + "s": + [ + 2.176, + 22.824, + 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": 0, + "s": + [ + 53.571, + 53.571, + 100 + ] + }, + { + "t": 29, + "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, + -1.1 + ], + [ + -1.1, + 0 + ], + [ + 0, + 1.1 + ], + [ + 1.1, + 0 + ] + ], + "o": + [ + [ + 0, + 1.1 + ], + [ + 1.1, + 0 + ], + [ + 0, + -1.1 + ], + [ + -1.1, + 0 + ] + ], + "v": + [ + [ + -2, + 0 + ], + [ + 0, + 2 + ], + [ + 2, + 0 + ], + [ + 0, + -2 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.22265625, + 0.411768317223, + 0.9375, + 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": "Dot", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_2", + "nm": "DuckDuckGo Icon", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Ring", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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": + [ + { + "d": 1, + "ty": "el", + "s": + { + "a": 0, + "k": + [ + 49, + 49 + ], + "ix": 2 + }, + "p": + { + "a": 0, + "k": + [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "st", + "c": + { + "a": 0, + "k": + [ + 1, + 1, + 1, + 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": "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": "Ring", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Eyes", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 80, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 26.452, + 22.241, + 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.951, + 0 + ], + [ + 0.262, + -0.429 + ], + [ + -0.105, + 0.057 + ], + [ + -0.783, + -0.011 + ], + [ + -0.348, + -0.152 + ], + [ + -0.024, + -0.01 + ], + [ + 0.063, + 0.086 + ] + ], + "o": + [ + [ + -0.951, + 0 + ], + [ + -0.062, + 0.102 + ], + [ + 0.311, + -0.169 + ], + [ + 0.732, + 0.01 + ], + [ + 0.024, + 0.011 + ], + [ + 0.098, + 0.042 + ], + [ + -0.31, + -0.422 + ] + ], + "v": + [ + [ + 5.971, + -3.949 + ], + [ + 4.176, + -3.189 + ], + [ + 4.383, + -3.03 + ], + [ + 5.971, + -3.384 + ], + [ + 7.478, + -3.035 + ], + [ + 7.549, + -3.004 + ], + [ + 7.679, + -3.148 + ] + ], + "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.108, + 0.026 + ], + [ + 0.924, + -0.386 + ], + [ + 0.179, + -0.285 + ], + [ + -0.001, + 0.125 + ], + [ + -1.037, + 0.387 + ], + [ + -0.398, + -0.321 + ] + ], + "o": + [ + [ + -0.513, + -0.121 + ], + [ + -0.924, + 0.386 + ], + [ + -0.066, + 0.105 + ], + [ + 0.003, + -0.528 + ], + [ + 1.163, + -0.434 + ], + [ + 0.086, + 0.07 + ] + ], + "v": + [ + [ + -4.372, + -2.854 + ], + [ + -6.536, + -2.639 + ], + [ + -7.936, + -1.553 + ], + [ + -8.258, + -1.582 + ], + [ + -6.729, + -3.301 + ], + [ + -4.276, + -3.015 + ] + ], + "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.813 + ], + [ + -0.817, + 0 + ], + [ + 0, + 0.813 + ], + [ + 0.817, + 0 + ] + ], + "o": + [ + [ + 0, + 0.813 + ], + [ + 0.817, + 0 + ], + [ + 0, + -0.813 + ], + [ + -0.817, + 0 + ] + ], + "v": + [ + [ + 5.299, + 1.21 + ], + [ + 6.779, + 2.684 + ], + [ + 8.258, + 1.21 + ], + [ + 6.779, + -0.263 + ] + ], + "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.211, + 0 + ], + [ + 0, + -0.211 + ], + [ + 0.212, + 0 + ], + [ + 0, + 0.211 + ] + ], + "o": + [ + [ + 0.211, + 0 + ], + [ + 0, + 0.211 + ], + [ + -0.211, + 0 + ], + [ + 0.002, + -0.211 + ] + ], + "v": + [ + [ + 7.437, + 0.341 + ], + [ + 7.82, + 0.722 + ], + [ + 7.437, + 1.103 + ], + [ + 7.055, + 0.722 + ] + ], + "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.951, + 0 + ], + [ + 0, + 0.949 + ], + [ + 0.951, + 0 + ], + [ + 0, + -0.949 + ] + ], + "o": + [ + [ + 0.953, + 0 + ], + [ + 0, + -0.949 + ], + [ + -0.951, + 0 + ], + [ + 0, + 0.949 + ] + ], + "v": + [ + [ + -4.746, + 3.949 + ], + [ + -3.022, + 2.23 + ], + [ + -4.746, + 0.512 + ], + [ + -6.47, + 2.23 + ] + ], + "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.245, + 0 + ], + [ + 0, + -0.245 + ], + [ + 0.247, + 0 + ], + [ + 0, + 0.245 + ] + ], + "o": + [ + [ + 0.247, + 0 + ], + [ + 0, + 0.245 + ], + [ + -0.247, + 0 + ], + [ + 0.002, + -0.247 + ] + ], + "v": + [ + [ + -3.976, + 1.216 + ], + [ + -3.53, + 1.661 + ], + [ + -3.976, + 2.105 + ], + [ + -4.423, + 1.661 + ] + ], + "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.078431375325, + 0.188235297799, + 0.494117647409, + 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": "Eyes", + "np": 8, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Beak", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 34.137, + 30.779, + 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": + [ + [ + 1.976, + -0.117 + ], + [ + 0.179, + -1.035 + ], + [ + -1.377, + -0.988 + ], + [ + -4.691, + 0.297 + ], + [ + 0.525, + 0.874 + ], + [ + 4.723, + 0.418 + ], + [ + -0.404, + 0.7 + ], + [ + -3.569, + 0.494 + ], + [ + -1.058, + 1.351 + ], + [ + 0.255, + 0.412 + ], + [ + 1.656, + -0.739 + ], + [ + 2.404, + -0.408 + ] + ], + "o": + [ + [ + -3.911, + 0.168 + ], + [ + -0.23, + 1.354 + ], + [ + 1.086, + 0.779 + ], + [ + 4.691, + -0.297 + ], + [ + -0.525, + -0.874 + ], + [ + -3.495, + -0.31 + ], + [ + 0.936, + -1.622 + ], + [ + 3.569, + -0.494 + ], + [ + 0.554, + -0.707 + ], + [ + -0.232, + -0.375 + ], + [ + -1.171, + 0.522 + ], + [ + -1.617, + 0.274 + ] + ], + "v": + [ + [ + -4.431, + -3.203 + ], + [ + -9.51, + -0.015 + ], + [ + -7.688, + 3.842 + ], + [ + 1.113, + 5.844 + ], + [ + 7.917, + 2.69 + ], + [ + -1.097, + 3.1 + ], + [ + -4.617, + 0.517 + ], + [ + 2.317, + -0.768 + ], + [ + 8.92, + -3.722 + ], + [ + 9.451, + -5.589 + ], + [ + 6.955, + -5.336 + ], + [ + 1.617, + -3.722 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 1, + 0.800000011921, + 0.200000047684, + 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": 3.15, + "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": "Beak", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Bow Tie", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 30.76, + 43.904, + 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.206, + 0.575 + ], + [ + 0.061, + 0.109 + ], + [ + 0, + -2.193 + ], + [ + -0.094, + 0.49 + ], + [ + 1.323, + 1.564 + ], + [ + 1.63, + -1.216 + ], + [ + 1.148, + -0.186 + ], + [ + -0.042, + -0.919 + ], + [ + -0.164, + -0.46 + ], + [ + -1.015, + 0.018 + ], + [ + -0.253, + 0.452 + ] + ], + "o": + [ + [ + -0.042, + -0.117 + ], + [ + 0.56, + 0.062 + ], + [ + 0, + 0.528 + ], + [ + 0.286, + -1.498 + ], + [ + -0.265, + -0.314 + ], + [ + -0.35, + -0.37 + ], + [ + -1.551, + 0.251 + ], + [ + -0.002, + 0.619 + ], + [ + 0.206, + 0.575 + ], + [ + 1.015, + -0.018 + ], + [ + 0.253, + -0.452 + ] + ], + "v": + [ + [ + 2.219, + -1.105 + ], + [ + 2.067, + -1.446 + ], + [ + 3.195, + 1.411 + ], + [ + 7.668, + 2.424 + ], + [ + 6.465, + -4.363 + ], + [ + 1.85, + -1.743 + ], + [ + -0.255, + -2.143 + ], + [ + -2.1, + -0.651 + ], + [ + -1.857, + 1.51 + ], + [ + -0.408, + 2.439 + ], + [ + 2.506, + 1.751 + ] + ], + "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.359, + -0.568 + ], + [ + -0.577, + -0.453 + ], + [ + 0.153, + 0.512 + ] + ], + "o": + [ + [ + -1.597, + -0.711 + ], + [ + -0.754, + 1.193 + ], + [ + 0.499, + 0.392 + ], + [ + -0.522, + -1.751 + ] + ], + "v": + [ + [ + -2.662, + -1.282 + ], + [ + -7.526, + -2.131 + ], + [ + -6.641, + 4.355 + ], + [ + -2.468, + 1.955 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "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.298039227724, + 0.729411780834, + 0.235294118524, + 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": "Bow Tie", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Bow Tie Shadow", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 30.473, + 44.451, + 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.31, + -0.439 + ], + [ + -0.582, + -0.27 + ], + [ + 1.478, + -0.222 + ], + [ + 1.442, + 0.539 + ], + [ + 0.017, + -0.032 + ], + [ + 1.133, + -0.026 + ], + [ + 0.226, + 0.319 + ], + [ + 0.562, + 0.361 + ], + [ + -0.81, + 1.126 + ], + [ + -1.824, + -0.691 + ], + [ + -1.155, + 0.201 + ] + ], + "o": + [ + [ + 1.485, + -1.232 + ], + [ + 0.684, + 0.317 + ], + [ + -0.567, + 0.085 + ], + [ + -0.013, + 0.043 + ], + [ + -0.285, + 0.528 + ], + [ + -0.749, + 0.017 + ], + [ + -1.403, + 1.191 + ], + [ + -1.076, + -0.692 + ], + [ + 0.741, + -1.03 + ], + [ + 0.285, + -0.48 + ], + [ + 1.32, + -0.23 + ] + ], + "v": + [ + [ + 2.218, + -2.151 + ], + [ + 6.522, + -4.718 + ], + [ + 7.412, + 2.72 + ], + [ + 2.864, + 1.695 + ], + [ + 2.819, + 1.808 + ], + [ + -0.437, + 2.626 + ], + [ + -1.778, + 2.142 + ], + [ + -6.43, + 4.699 + ], + [ + -7.708, + -2.448 + ], + [ + -2.076, + -1.72 + ], + [ + 0.002, + -2.68 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.236603736877, + 0.660000026226, + 0.17018866539, + 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": "Bow Tie Shadow", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Subtract", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 26.052, + 30.837, + 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": + [ + [ + -2.029, + 0 + ], + [ + -1.224, + 0.479 + ], + [ + -2.741, + 0.426 + ], + [ + 0.048, + 0.089 + ], + [ + -2.974, + -0.333 + ], + [ + -0.174, + 0.108 + ], + [ + 7.644, + 1.642 + ], + [ + 0.02, + 0.025 + ], + [ + 3.984, + -0.536 + ], + [ + -0.158, + -0.025 + ], + [ + -0.348, + -0.109 + ], + [ + 0.11, + -0.053 + ], + [ + 0.551, + -0.759 + ], + [ + -0.115, + 0.04 + ], + [ + -1.186, + -1.407 + ], + [ + 0.095, + -0.022 + ], + [ + -2.31, + -9.434 + ], + [ + 0, + 0 + ], + [ + -0.064, + -0.261 + ], + [ + -0.657, + -2.023 + ] + ], + "o": + [ + [ + 1.384, + 0 + ], + [ + -2.582, + -5.658 + ], + [ + 0.1, + -0.016 + ], + [ + -4.861, + -9.059 + ], + [ + 0.734, + 0.082 + ], + [ + 0.881, + -0.55 + ], + [ + -0.031, + -0.007 + ], + [ + -2.087, + -2.629 + ], + [ + -0.158, + 0.021 + ], + [ + 0.666, + 0.107 + ], + [ + 0.116, + 0.036 + ], + [ + -0.532, + 0.256 + ], + [ + -0.072, + 0.099 + ], + [ + 2.81, + -0.969 + ], + [ + 0.063, + 0.074 + ], + [ + -10.339, + 2.421 + ], + [ + 0, + 0 + ], + [ + 0.063, + 0.258 + ], + [ + 1.943, + 7.964 + ], + [ + 1.064, + 0.479 + ] + ], + "v": + [ + [ + 2.503, + 20.785 + ], + [ + 7.633, + 20.052 + ], + [ + 4.171, + 5.209 + ], + [ + 4.286, + 4.986 + ], + [ + 8.29, + -3.082 + ], + [ + 9.753, + -3.046 + ], + [ + 2.443, + -17.321 + ], + [ + 2.364, + -17.37 + ], + [ + -7.867, + -20.659 + ], + [ + -7.854, + -20.343 + ], + [ + -6.248, + -19.96 + ], + [ + -6.235, + -19.71 + ], + [ + -8.394, + -18.11 + ], + [ + -8.25, + -17.935 + ], + [ + -2.032, + -17.036 + ], + [ + -2.11, + -16.827 + ], + [ + -8.253, + 2.733 + ], + [ + -8.251, + 2.74 + ], + [ + -8.061, + 3.518 + ], + [ + -3.408, + 20.003 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 1, + 1, + 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": "Subtract", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Head Shadow", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 24.847, + 31.379, + 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": + [ + [ + -1.799, + -3.602 + ], + [ + -1.14, + -2.216 + ], + [ + 0, + 0 + ], + [ + 2.079, + 1.141 + ], + [ + 0.01, + 0.032 + ], + [ + 2.531, + 10.147 + ], + [ + -10.089, + 2.742 + ], + [ + 0.075, + 0.069 + ], + [ + 1.932, + -0.382 + ], + [ + -0.104, + 0.088 + ], + [ + -0.376, + 0.014 + ], + [ + 0.075, + 0.094 + ], + [ + 0.557, + 0.189 + ], + [ + -0.151, + 0.02 + ], + [ + -0.827, + -0.132 + ], + [ + -1.449, + -0.772 + ], + [ + 0.15, + -0.595 + ] + ], + "o": + [ + [ + 0.677, + 1.349 + ], + [ + 0, + 0 + ], + [ + 0.464, + 1.307 + ], + [ + -0.029, + -0.016 + ], + [ + -0.145, + -0.475 + ], + [ + -2.686, + -10.77 + ], + [ + 0.098, + -0.027 + ], + [ + -1.163, + -1.057 + ], + [ + -0.134, + 0.026 + ], + [ + 0.899, + -0.763 + ], + [ + 0.12, + -0.004 + ], + [ + -0.398, + -0.495 + ], + [ + -0.144, + -0.049 + ], + [ + 0.848, + -0.114 + ], + [ + 1.63, + 0.273 + ], + [ + 1.303, + 0.69 + ], + [ + -0.822, + 3.275 + ] + ], + "v": + [ + [ + 6.721, + 15.708 + ], + [ + 9.628, + 21.312 + ], + [ + 9.03, + 20.157 + ], + [ + -3.519, + 19.63 + ], + [ + -3.576, + 19.557 + ], + [ + -7.451, + 4.932 + ], + [ + -2.045, + -17.355 + ], + [ + -1.983, + -17.573 + ], + [ + -7.05, + -18.436 + ], + [ + -7.166, + -18.662 + ], + [ + -5.21, + -19.597 + ], + [ + -5.064, + -19.842 + ], + [ + -6.695, + -20.853 + ], + [ + -6.682, + -21.193 + ], + [ + -3.923, + -21.238 + ], + [ + 0.939, + -18.968 + ], + [ + 3.937, + 3.754 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.867647051811, + 0.867647051811, + 0.867647051811, + 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": "Head Shadow", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 8, + "ty": 4, + "nm": "DDG_App_Background", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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": + [ + [ + 1.342, + -2.634 + ], + [ + 0, + -7.841 + ], + [ + 0, + 0 + ], + [ + -1.526, + -2.995 + ], + [ + -2.634, + -1.342 + ], + [ + -7.841, + 0 + ], + [ + 0, + 0 + ], + [ + -2.995, + 1.526 + ], + [ + -1.342, + 2.634 + ], + [ + 0, + 7.841 + ], + [ + 0, + 0 + ], + [ + 1.526, + 2.995 + ], + [ + 2.634, + 1.342 + ], + [ + 7.841, + 0 + ], + [ + 0, + 0 + ], + [ + 2.995, + -1.526 + ] + ], + "o": + [ + [ + -1.526, + 2.995 + ], + [ + 0, + 0 + ], + [ + 0, + 7.841 + ], + [ + 1.342, + 2.634 + ], + [ + 2.995, + 1.526 + ], + [ + 0, + 0 + ], + [ + 7.841, + 0 + ], + [ + 2.634, + -1.342 + ], + [ + 1.526, + -2.995 + ], + [ + 0, + 0 + ], + [ + 0, + -7.841 + ], + [ + -1.342, + -2.634 + ], + [ + -2.995, + -1.526 + ], + [ + 0, + 0 + ], + [ + -7.841, + 0 + ], + [ + -2.634, + 1.342 + ] + ], + "v": + [ + [ + -26.474, + -20.356 + ], + [ + -28, + -5.6 + ], + [ + -28, + 5.6 + ], + [ + -26.474, + 20.356 + ], + [ + -20.356, + 26.474 + ], + [ + -5.6, + 28 + ], + [ + 5.6, + 28 + ], + [ + 20.356, + 26.474 + ], + [ + 26.474, + 20.356 + ], + [ + 28, + 5.6 + ], + [ + 28, + -5.6 + ], + [ + 26.474, + -20.356 + ], + [ + 20.356, + -26.474 + ], + [ + 5.6, + -28 + ], + [ + -5.6, + -28 + ], + [ + -20.356, + -26.474 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.870588243008, + 0.345098048449, + 0.20000000298, + 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": "Backdrop", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_3", + "nm": "Mail Icon", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Mail Icon", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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, + 0 + ], + [ + -1.672, + 0 + ], + [ + -0.617, + 0.186 + ], + [ + -0.788, + 0.788 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.058, + -0.03 + ], + [ + 1.568, + 0 + ], + [ + 0, + 0 + ], + [ + 0.599, + 0.305 + ], + [ + 0.055, + 0.033 + ] + ], + "o": + [ + [ + 0, + 0 + ], + [ + 1.276, + 1.276 + ], + [ + 0.639, + 0 + ], + [ + 0.997, + -0.301 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.056, + 0.034 + ], + [ + -0.599, + 0.305 + ], + [ + 0, + 0 + ], + [ + -1.568, + 0 + ], + [ + -0.058, + -0.029 + ], + [ + 0, + 0 + ] + ], + "v": + [ + [ + -7.115, + 0.92 + ], + [ + -4.618, + 3.416 + ], + [ + 0.002, + 5.33 + ], + [ + 1.897, + 5.05 + ], + [ + 4.621, + 3.416 + ], + [ + 7.118, + 0.92 + ], + [ + 18.554, + 12.355 + ], + [ + 18.382, + 12.45 + ], + [ + 15.431, + 12.756 + ], + [ + -15.431, + 12.756 + ], + [ + -18.382, + 12.45 + ], + [ + -18.552, + 12.356 + ] + ], + "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.305, + -0.599 + ], + [ + 0.033, + -0.056 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.038, + -0.075 + ], + [ + 0, + -1.568 + ], + [ + 0, + 0 + ] + ], + "o": + [ + [ + -0.03, + 0.058 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.044, + 0.071 + ], + [ + 0.305, + 0.599 + ], + [ + 0, + 0 + ], + [ + 0, + 1.568 + ] + ], + "v": + [ + [ + 19.606, + 11.227 + ], + [ + 19.511, + 11.397 + ], + [ + 8.076, + -0.038 + ], + [ + 19.482, + -11.445 + ], + [ + 19.606, + -11.227 + ], + [ + 19.911, + -8.276 + ], + [ + 19.911, + 8.276 + ] + ], + "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.775, + 0.775 + ], + [ + 0, + 0 + ], + [ + 0.076, + 0.039 + ], + [ + 1.568, + 0 + ], + [ + 0, + 0 + ], + [ + 0.599, + -0.305 + ], + [ + 0.071, + -0.044 + ], + [ + 0, + 0 + ], + [ + -0.981, + -0.2 + ], + [ + -0.684, + 0.132 + ] + ], + "o": + [ + [ + 0, + 0 + ], + [ + -0.071, + -0.045 + ], + [ + -0.599, + -0.305 + ], + [ + 0, + 0 + ], + [ + -1.568, + 0 + ], + [ + -0.075, + 0.038 + ], + [ + 0, + 0 + ], + [ + 0.761, + 0.761 + ], + [ + 0.683, + 0.139 + ], + [ + 1, + -0.194 + ] + ], + "v": + [ + [ + 3.741, + 2.536 + ], + [ + 18.603, + -12.325 + ], + [ + 18.382, + -12.45 + ], + [ + 15.431, + -12.756 + ], + [ + -15.431, + -12.756 + ], + [ + -18.382, + -12.45 + ], + [ + -18.601, + -12.326 + ], + [ + -3.738, + 2.536 + ], + [ + -1.061, + 3.978 + ], + [ + 1.012, + 3.989 + ] + ], + "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.305, + 0.599 + ], + [ + -0.045, + 0.071 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.03, + 0.059 + ], + [ + 0, + 1.568 + ], + [ + 0, + 0 + ] + ], + "o": + [ + [ + 0.038, + -0.075 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.034, + -0.056 + ], + [ + -0.305, + -0.599 + ], + [ + 0, + 0 + ], + [ + 0, + -1.568 + ] + ], + "v": + [ + [ + -19.606, + -11.227 + ], + [ + -19.481, + -11.447 + ], + [ + -8.073, + -0.038 + ], + [ + -19.51, + 11.399 + ], + [ + -19.606, + 11.227 + ], + [ + -19.911, + 8.276 + ], + [ + -19.911, + -8.276 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 4", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 1, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.969, + 0.969, + 0.969, + 0.5, + 0.973, + 0.973, + 0.973, + 1, + 0.976, + 0.976, + 0.976 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 0, + -12.756 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 0, + 12.756 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Mail Icon", + "np": 6, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Blue Gradient", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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": + [ + [ + 1.342, + -2.634 + ], + [ + 0, + -7.841 + ], + [ + 0, + 0 + ], + [ + -1.526, + -2.995 + ], + [ + -2.634, + -1.342 + ], + [ + -7.841, + 0 + ], + [ + 0, + 0 + ], + [ + -2.995, + 1.526 + ], + [ + -1.342, + 2.634 + ], + [ + 0, + 7.841 + ], + [ + 0, + 0 + ], + [ + 1.526, + 2.995 + ], + [ + 2.634, + 1.342 + ], + [ + 7.841, + 0 + ], + [ + 0, + 0 + ], + [ + 2.995, + -1.526 + ] + ], + "o": + [ + [ + -1.526, + 2.995 + ], + [ + 0, + 0 + ], + [ + 0, + 7.841 + ], + [ + 1.342, + 2.634 + ], + [ + 2.995, + 1.526 + ], + [ + 0, + 0 + ], + [ + 7.841, + 0 + ], + [ + 2.634, + -1.342 + ], + [ + 1.526, + -2.995 + ], + [ + 0, + 0 + ], + [ + 0, + -7.841 + ], + [ + -1.342, + -2.634 + ], + [ + -2.995, + -1.526 + ], + [ + 0, + 0 + ], + [ + -7.841, + 0 + ], + [ + -2.634, + 1.342 + ] + ], + "v": + [ + [ + -26.474, + -20.356 + ], + [ + -28, + -5.6 + ], + [ + -28, + 5.6 + ], + [ + -26.474, + 20.356 + ], + [ + -20.356, + 26.474 + ], + [ + -5.6, + 28 + ], + [ + 5.6, + 28 + ], + [ + 20.356, + 26.474 + ], + [ + 26.474, + 20.356 + ], + [ + 28, + 5.6 + ], + [ + 28, + -5.6 + ], + [ + 26.474, + -20.356 + ], + [ + 20.356, + -26.474 + ], + [ + 5.6, + -28 + ], + [ + -5.6, + -28 + ], + [ + -20.356, + -26.474 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.102, + 0.416, + 0.906, + 0.5, + 0.114, + 0.588, + 0.931, + 1, + 0.125, + 0.761, + 0.957 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 0, + -28 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 0, + 28 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Blue Gradient", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Mail Icon", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 27.998, + 28.233, + 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": + [ + 39.822, + 25.511 + ], + "ix": 2 + }, + "p": + { + "a": 0, + "k": + [ + 0, + 0 + ], + "ix": 3 + }, + "r": + { + "a": 0, + "k": 3, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ind": 1, + "ty": "sh", + "ix": 2, + "ks": + { + "a": 0, + "k": + { + "i": + [ + [ + 1.276, + 1.276 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.981, + -0.2 + ], + [ + -0.684, + 0.132 + ], + [ + -0.775, + 0.775 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.997, + -0.301 + ], + [ + 0.639, + 0 + ] + ], + "o": + [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0.761, + 0.761 + ], + [ + 0.683, + 0.139 + ], + [ + 1, + -0.194 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -0.788, + 0.788 + ], + [ + -0.617, + 0.186 + ], + [ + -1.672, + 0 + ] + ], + "v": + [ + [ + -4.618, + 3.416 + ], + [ + -7.115, + 0.92 + ], + [ + -19.547, + 13.352 + ], + [ + -19.625, + 13.274 + ], + [ + -19.703, + 13.352 + ], + [ + -20.583, + 12.472 + ], + [ + -8.073, + -0.038 + ], + [ + -20.972, + -12.937 + ], + [ + -20.092, + -13.817 + ], + [ + -3.738, + 2.536 + ], + [ + -1.061, + 3.978 + ], + [ + 1.012, + 3.989 + ], + [ + 3.741, + 2.536 + ], + [ + 20.095, + -13.817 + ], + [ + 20.975, + -12.937 + ], + [ + 8.076, + -0.038 + ], + [ + 20.586, + 12.472 + ], + [ + 19.706, + 13.352 + ], + [ + 19.628, + 13.274 + ], + [ + 19.551, + 13.352 + ], + [ + 7.118, + 0.92 + ], + [ + 4.621, + 3.416 + ], + [ + 1.897, + 5.05 + ], + [ + 0.002, + 5.33 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "mm", + "mm": 3, + "nm": "Merge Paths 1", + "mn": "ADBE Vector Filter - Merge", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.969, + 0.969, + 0.969, + 0.5, + 0.973, + 0.973, + 0.973, + 1, + 0.976, + 0.976, + 0.976 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 0, + -12.756 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 0, + 12.756 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Mail Icon", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Blue Gradient", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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": + [ + [ + 1.342, + -2.634 + ], + [ + 0, + -7.841 + ], + [ + 0, + 0 + ], + [ + -1.526, + -2.995 + ], + [ + -2.634, + -1.342 + ], + [ + -7.841, + 0 + ], + [ + 0, + 0 + ], + [ + -2.995, + 1.526 + ], + [ + -1.342, + 2.634 + ], + [ + 0, + 7.841 + ], + [ + 0, + 0 + ], + [ + 1.526, + 2.995 + ], + [ + 2.634, + 1.342 + ], + [ + 7.841, + 0 + ], + [ + 0, + 0 + ], + [ + 2.995, + -1.526 + ] + ], + "o": + [ + [ + -1.526, + 2.995 + ], + [ + 0, + 0 + ], + [ + 0, + 7.841 + ], + [ + 1.342, + 2.634 + ], + [ + 2.995, + 1.526 + ], + [ + 0, + 0 + ], + [ + 7.841, + 0 + ], + [ + 2.634, + -1.342 + ], + [ + 1.526, + -2.995 + ], + [ + 0, + 0 + ], + [ + 0, + -7.841 + ], + [ + -1.342, + -2.634 + ], + [ + -2.995, + -1.526 + ], + [ + 0, + 0 + ], + [ + -7.841, + 0 + ], + [ + -2.634, + 1.342 + ] + ], + "v": + [ + [ + -26.474, + -20.356 + ], + [ + -28, + -5.6 + ], + [ + -28, + 5.6 + ], + [ + -26.474, + 20.356 + ], + [ + -20.356, + 26.474 + ], + [ + -5.6, + 28 + ], + [ + 5.6, + 28 + ], + [ + 20.356, + 26.474 + ], + [ + 26.474, + 20.356 + ], + [ + 28, + 5.6 + ], + [ + 28, + -5.6 + ], + [ + 26.474, + -20.356 + ], + [ + 20.356, + -26.474 + ], + [ + 5.6, + -28 + ], + [ + -5.6, + -28 + ], + [ + -20.356, + -26.474 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.102, + 0.416, + 0.906, + 0.5, + 0.114, + 0.588, + 0.931, + 1, + 0.125, + 0.761, + 0.957 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 0, + -28 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 0, + 28 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Blue Gradient", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_4", + "nm": "Music Icon", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Apple Music Icon", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 26.134, + 27.519, + 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.868, + 0.179 + ], + [ + 0, + 0 + ], + [ + 0, + -0.788 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 1.815, + -0.588 + ], + [ + 0.844, + 0 + ], + [ + 0, + 2.406 + ], + [ + -0.008, + 0.1 + ], + [ + 0.004, + 0.001 + ], + [ + 0, + 0.003 + ], + [ + -2.953, + 0.155 + ], + [ + 0.007, + 0.954 + ], + [ + 0, + 0.043 + ], + [ + 0, + 0 + ], + [ + 0.579, + -0.119 + ], + [ + 0, + 0 + ], + [ + 0, + -0.443 + ], + [ + 0, + 0 + ], + [ + 1.816, + -0.587 + ], + [ + 0.843, + 0 + ], + [ + 0, + 2.406 + ], + [ + -0.008, + 0.1 + ], + [ + 0.004, + 0.001 + ], + [ + 0, + 0.004 + ], + [ + -2.953, + 0.155 + ], + [ + 0.007, + 0.954 + ], + [ + 0, + 0.043 + ], + [ + 0, + 0 + ] + ], + "o": + [ + [ + 0, + -0.886 + ], + [ + 0, + 0 + ], + [ + 0.772, + -0.159 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 0, + 3.768 + ], + [ + -0.697, + 0.324 + ], + [ + -2.749, + 0 + ], + [ + 0, + -0.102 + ], + [ + 0, + -0.004 + ], + [ + -0.003, + -0.001 + ], + [ + 0.005, + -2.486 + ], + [ + 2.819, + -0.148 + ], + [ + 0, + -0.046 + ], + [ + 0, + 0 + ], + [ + 0, + -0.591 + ], + [ + 0, + 0 + ], + [ + -0.434, + 0.089 + ], + [ + 0, + 0 + ], + [ + 0, + 3.77 + ], + [ + -0.696, + 0.324 + ], + [ + -2.749, + 0 + ], + [ + 0, + -0.102 + ], + [ + 0, + -0.004 + ], + [ + -0.003, + -0.001 + ], + [ + 0.005, + -2.486 + ], + [ + 2.819, + -0.148 + ], + [ + 0, + -0.046 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": + [ + [ + -6.222, + -13.553 + ], + [ + -4.732, + -15.382 + ], + [ + 13.438, + -19.122 + ], + [ + 14.933, + -17.904 + ], + [ + 14.933, + -11.03 + ], + [ + 14.934, + -11.029 + ], + [ + 14.934, + 8.882 + ], + [ + 11.359, + 14.751 + ], + [ + 9.023, + 15.26 + ], + [ + 4.045, + 10.904 + ], + [ + 4.057, + 10.601 + ], + [ + 4.051, + 10.594 + ], + [ + 4.045, + 10.587 + ], + [ + 9.956, + 5.926 + ], + [ + 12.757, + 3.26 + ], + [ + 12.756, + 3.126 + ], + [ + 12.756, + -8.815 + ], + [ + 11.635, + -9.729 + ], + [ + -3.3, + -6.654 + ], + [ + -4.045, + -5.74 + ], + [ + -4.045, + 12.771 + ], + [ + -7.623, + 18.642 + ], + [ + -9.956, + 19.148 + ], + [ + -14.934, + 14.793 + ], + [ + -14.922, + 14.491 + ], + [ + -14.928, + 14.483 + ], + [ + -14.934, + 14.476 + ], + [ + -9.023, + 9.815 + ], + [ + -6.222, + 7.149 + ], + [ + -6.223, + 7.015 + ], + [ + -6.222, + -7.141 + ] + ], + "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 + ] + ], + "o": + [ + [ + 0, + 0 + ] + ], + "v": + [ + [ + -6.222, + -7.141 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 2", + "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, + 1, + 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": "Apple Music Icon", + "np": 4, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Red/Pink Gradient", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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": + [ + [ + 1.342, + -2.634 + ], + [ + 0, + -7.841 + ], + [ + 0, + 0 + ], + [ + -1.526, + -2.995 + ], + [ + -2.634, + -1.342 + ], + [ + -7.841, + 0 + ], + [ + 0, + 0 + ], + [ + -2.995, + 1.526 + ], + [ + -1.342, + 2.634 + ], + [ + 0, + 7.841 + ], + [ + 0, + 0 + ], + [ + 1.526, + 2.995 + ], + [ + 2.634, + 1.342 + ], + [ + 7.841, + 0 + ], + [ + 0, + 0 + ], + [ + 2.995, + -1.526 + ] + ], + "o": + [ + [ + -1.526, + 2.995 + ], + [ + 0, + 0 + ], + [ + 0, + 7.841 + ], + [ + 1.342, + 2.634 + ], + [ + 2.995, + 1.526 + ], + [ + 0, + 0 + ], + [ + 7.841, + 0 + ], + [ + 2.634, + -1.342 + ], + [ + 1.526, + -2.995 + ], + [ + 0, + 0 + ], + [ + 0, + -7.841 + ], + [ + -1.342, + -2.634 + ], + [ + -2.995, + -1.526 + ], + [ + 0, + 0 + ], + [ + -7.841, + 0 + ], + [ + -2.634, + 1.342 + ] + ], + "v": + [ + [ + -26.474, + -20.356 + ], + [ + -28, + -5.6 + ], + [ + -28, + 5.6 + ], + [ + -26.474, + 20.356 + ], + [ + -20.356, + 26.474 + ], + [ + -5.6, + 28 + ], + [ + 5.6, + 28 + ], + [ + 20.356, + 26.474 + ], + [ + 26.474, + 20.356 + ], + [ + 28, + 5.6 + ], + [ + 28, + -5.6 + ], + [ + 26.474, + -20.356 + ], + [ + 20.356, + -26.474 + ], + [ + 5.6, + -28 + ], + [ + -5.6, + -28 + ], + [ + -20.356, + -26.474 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.984, + 0.349, + 0.463, + 0.5, + 0.982, + 0.245, + 0.347, + 1, + 0.98, + 0.141, + 0.231 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 0, + -28 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 0, + 28 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Red/Pink Gradient", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_5", + "nm": "Phone Icon", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Phone Icon", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 26, + 29.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, + 2.419 + ], + [ + -12.6, + 0 + ], + [ + 0, + -4.822 + ], + [ + 0.575, + -0.883 + ], + [ + 0.45, + 0.1 + ], + [ + 0, + 0 + ], + [ + 3.354, + 0.629 + ], + [ + 0.373, + 1.742 + ], + [ + 0.622, + 0.622 + ], + [ + 5.444, + 0 + ], + [ + 0.622, + -0.622 + ], + [ + 0.467, + -2.178 + ], + [ + 0.83, + -0.156 + ], + [ + 1.971, + -0.437 + ], + [ + 0.071, + -0.016 + ], + [ + 0.252, + 0.386 + ] + ], + "o": + [ + [ + 0, + -4.822 + ], + [ + 12.6, + 0 + ], + [ + 0, + 2.419 + ], + [ + -0.252, + 0.386 + ], + [ + 0, + 0 + ], + [ + -1.971, + -0.437 + ], + [ + -0.83, + -0.156 + ], + [ + -0.467, + -2.178 + ], + [ + -0.622, + -0.622 + ], + [ + -5.444, + 0 + ], + [ + -0.622, + 0.622 + ], + [ + -0.373, + 1.742 + ], + [ + -3.354, + 0.629 + ], + [ + -0.075, + 0.017 + ], + [ + -0.45, + 0.1 + ], + [ + -0.575, + -0.883 + ] + ], + "v": + [ + [ + -22.711, + 1.855 + ], + [ + 0, + -7.167 + ], + [ + 22.711, + 1.855 + ], + [ + 21.446, + 6.698 + ], + [ + 20.268, + 7.138 + ], + [ + 20.049, + 7.089 + ], + [ + 11.356, + 5.277 + ], + [ + 9.644, + 2.944 + ], + [ + 8.089, + -0.634 + ], + [ + 0, + -1.723 + ], + [ + -8.089, + -0.634 + ], + [ + -9.644, + 2.944 + ], + [ + -11.356, + 5.277 + ], + [ + -20.049, + 7.089 + ], + [ + -20.268, + 7.138 + ], + [ + -21.446, + 6.698 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 1, + 1, + 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": 45, + "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": "Phone Icon", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Green Gradient", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 28, + 28, + 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": + [ + [ + 1.342, + -2.634 + ], + [ + 0, + -7.841 + ], + [ + 0, + 0 + ], + [ + -1.526, + -2.995 + ], + [ + -2.634, + -1.342 + ], + [ + -7.841, + 0 + ], + [ + 0, + 0 + ], + [ + -2.995, + 1.526 + ], + [ + -1.342, + 2.634 + ], + [ + 0, + 7.841 + ], + [ + 0, + 0 + ], + [ + 1.526, + 2.995 + ], + [ + 2.634, + 1.342 + ], + [ + 7.841, + 0 + ], + [ + 0, + 0 + ], + [ + 2.995, + -1.526 + ] + ], + "o": + [ + [ + -1.526, + 2.995 + ], + [ + 0, + 0 + ], + [ + 0, + 7.841 + ], + [ + 1.342, + 2.634 + ], + [ + 2.995, + 1.526 + ], + [ + 0, + 0 + ], + [ + 7.841, + 0 + ], + [ + 2.634, + -1.342 + ], + [ + 1.526, + -2.995 + ], + [ + 0, + 0 + ], + [ + 0, + -7.841 + ], + [ + -1.342, + -2.634 + ], + [ + -2.995, + -1.526 + ], + [ + 0, + 0 + ], + [ + -7.841, + 0 + ], + [ + -2.634, + 1.342 + ] + ], + "v": + [ + [ + -26.474, + -20.356 + ], + [ + -28, + -5.6 + ], + [ + -28, + 5.6 + ], + [ + -26.474, + 20.356 + ], + [ + -20.356, + 26.474 + ], + [ + -5.6, + 28 + ], + [ + 5.6, + 28 + ], + [ + 20.356, + 26.474 + ], + [ + 26.474, + 20.356 + ], + [ + 28, + 5.6 + ], + [ + 28, + -5.6 + ], + [ + 26.474, + -20.356 + ], + [ + 20.356, + -26.474 + ], + [ + 5.6, + -28 + ], + [ + -5.6, + -28 + ], + [ + -20.356, + -26.474 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.373, + 0.965, + 0.467, + 0.5, + 0.206, + 0.849, + 0.322, + 1, + 0.039, + 0.733, + 0.176 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 0, + -28 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 0, + 28 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Green Gradient", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_6", + "nm": "Background-Vector", + "fr": 60, + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 2, + "nm": "Dock-Blur", + "refId": "image_0", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 155, + 52, + 0 + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 294, + 88, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 50, + 50, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Stroke", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 153.914, + 49.174, + 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": + [ + [ + -29.295, + 13.198 + ], + [ + -48.757, + 6.506 + ], + [ + -0.127, + -0.25 + ], + [ + -0.106, + -0.227 + ], + [ + 13.03, + -6.452 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 68.15, + 0 + ], + [ + 11.373, + 1.034 + ], + [ + 0.087, + 0.525 + ], + [ + -13.912, + 0 + ] + ], + "o": + [ + [ + 13.165, + -6.516 + ], + [ + 0.134, + 0.246 + ], + [ + 0.113, + 0.222 + ], + [ + -48.931, + 6.483 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -29.38, + 13.239 + ], + [ + -13.786, + 0 + ], + [ + -0.103, + -0.497 + ], + [ + 11.436, + 1.053 + ], + [ + 67.841, + 0 + ] + ], + "v": + [ + [ + 57.766, + -8.635 + ], + [ + 152.207, + -32.445 + ], + [ + 152.598, + -31.702 + ], + [ + 152.927, + -31.028 + ], + [ + 58.419, + -7.285 + ], + [ + 58.407, + -7.279 + ], + [ + 58.394, + -7.273 + ], + [ + -114.914, + 32.445 + ], + [ + -152.644, + 30.803 + ], + [ + -152.927, + 29.271 + ], + [ + -114.914, + 30.945 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.016, + 0.106, + 0.137, + 0.5, + 0.008, + 0.178, + 0.569, + 1, + 0, + 0.25, + 1, + 0, + 1, + 0.5, + 0.82, + 1, + 0.64 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + -152.927, + 0 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 152.927, + 0 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Stroke", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Wave Front", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 155.56, + 60.718, + 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": + [ + [ + 3.488, + 6.845 + ], + [ + 0.006, + 0.012 + ], + [ + 13.094, + -6.484 + ], + [ + 68, + 0 + ], + [ + 11.407, + 1.043 + ], + [ + -1.099, + -2.158 + ], + [ + -6.021, + -3.068 + ], + [ + -17.922, + 0 + ], + [ + 0, + 0 + ], + [ + -6.845, + 3.488 + ], + [ + -3.068, + 6.021 + ], + [ + 0, + 17.922 + ], + [ + 0, + 0 + ] + ], + "o": + [ + [ + -0.006, + -0.012 + ], + [ + -48.858, + 6.495 + ], + [ + -29.333, + 13.217 + ], + [ + -13.852, + 0 + ], + [ + 0.516, + 2.792 + ], + [ + 3.068, + 6.021 + ], + [ + 6.845, + 3.488 + ], + [ + 0, + 0 + ], + [ + 17.922, + 0 + ], + [ + 6.021, + -3.068 + ], + [ + 3.488, + -6.845 + ], + [ + 0, + 0 + ], + [ + 0, + -17.922 + ] + ], + "v": + [ + [ + 150.952, + -43.245 + ], + [ + 150.933, + -43.282 + ], + [ + 56.44, + -19.5 + ], + [ + -116.56, + 20.152 + ], + [ + -154.44, + 18.492 + ], + [ + -152.072, + 25.81 + ], + [ + -138.088, + 39.794 + ], + [ + -104.36, + 43.282 + ], + [ + 103.24, + 43.282 + ], + [ + 136.968, + 39.794 + ], + [ + 150.952, + 25.81 + ], + [ + 154.44, + -7.918 + ], + [ + 154.44, + -9.518 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 8, + "g": + { + "p": 5, + "k": + { + "a": 0, + "k": + [ + 0, + 0.02, + 0.11, + 0.153, + 0.353, + 0.01, + 0.194, + 0.541, + 0.706, + 0, + 0.279, + 0.93, + 0.853, + 0, + 0.319, + 0.965, + 1, + 0, + 0.36, + 1, + 0, + 1, + 0.353, + 0.82, + 0.706, + 0.64, + 0.853, + 0.4, + 1, + 0.16 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + -134.56, + -22.718 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 166.94, + 0.282 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Wave Front", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 8 + }, + { + "ddd": 0, + "ind": 4, + "ty": 4, + "nm": "Wave Back", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 90.16, + 52, + 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, + "ef": + [ + { + "ty": 25, + "nm": "Drop Shadow", + "np": 8, + "mn": "ADBE Drop Shadow", + "ix": 1, + "en": 1, + "ef": + [ + { + "ty": 2, + "nm": "Shadow Color", + "mn": "ADBE Drop Shadow-0001", + "ix": 1, + "v": + { + "a": 0, + "k": + [ + 0, + 0.32382813096, + 0.971484363079, + 0.319999992847 + ], + "ix": 1 + } + }, + { + "ty": 0, + "nm": "Opacity", + "mn": "ADBE Drop Shadow-0002", + "ix": 2, + "v": + { + "a": 0, + "k": 102, + "ix": 2 + } + }, + { + "ty": 0, + "nm": "Direction", + "mn": "ADBE Drop Shadow-0003", + "ix": 3, + "v": + { + "a": 0, + "k": 90, + "ix": 3 + } + }, + { + "ty": 0, + "nm": "Distance", + "mn": "ADBE Drop Shadow-0004", + "ix": 4, + "v": + { + "a": 0, + "k": 2, + "ix": 4 + } + }, + { + "ty": 0, + "nm": "Softness", + "mn": "ADBE Drop Shadow-0005", + "ix": 5, + "v": + { + "a": 0, + "k": 0, + "ix": 5 + } + }, + { + "ty": 7, + "nm": "Shadow Only", + "mn": "ADBE Drop Shadow-0006", + "ix": 6, + "v": + { + "a": 0, + "k": 0, + "ix": 6 + } + } + ] + }, + { + "ty": 25, + "nm": "Drop Shadow 2", + "np": 8, + "mn": "ADBE Drop Shadow", + "ix": 2, + "en": 1, + "ef": + [ + { + "ty": 2, + "nm": "Shadow Color", + "mn": "ADBE Drop Shadow-0001", + "ix": 1, + "v": + { + "a": 0, + "k": + [ + 0, + 0.32549020648, + 0.972549021244, + 0.800000011921 + ], + "ix": 1 + } + }, + { + "ty": 0, + "nm": "Opacity", + "mn": "ADBE Drop Shadow-0002", + "ix": 2, + "v": + { + "a": 0, + "k": 255, + "ix": 2 + } + }, + { + "ty": 0, + "nm": "Direction", + "mn": "ADBE Drop Shadow-0003", + "ix": 3, + "v": + { + "a": 0, + "k": 90, + "ix": 3 + } + }, + { + "ty": 0, + "nm": "Distance", + "mn": "ADBE Drop Shadow-0004", + "ix": 4, + "v": + { + "a": 0, + "k": 4, + "ix": 4 + } + }, + { + "ty": 0, + "nm": "Softness", + "mn": "ADBE Drop Shadow-0005", + "ix": 5, + "v": + { + "a": 0, + "k": 4, + "ix": 5 + } + }, + { + "ty": 7, + "nm": "Shadow Only", + "mn": "ADBE Drop Shadow-0006", + "ix": 6, + "v": + { + "a": 0, + "k": 0, + "ix": 6 + } + } + ] + } + ], + "sy": + [ + { + "c": + { + "a": 0, + "k": + [ + 1, + 1, + 1, + 0.639999985695 + ], + "ix": 2 + }, + "o": + { + "a": 0, + "k": 64, + "ix": 3 + }, + "a": + { + "a": 0, + "k": 135, + "ix": 5 + }, + "s": + { + "a": 0, + "k": 25, + "ix": 8 + }, + "d": + { + "a": 0, + "k": 1.414, + "ix": 6 + }, + "ch": + { + "a": 0, + "k": 0, + "ix": 7 + }, + "bm": + { + "a": 0, + "k": 1, + "ix": 1 + }, + "no": + { + "a": 0, + "k": 0, + "ix": 9 + }, + "ty": 2, + "nm": "Inner Shadow" + } + ], + "shapes": + [ + { + "ty": "gr", + "it": + [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": + { + "a": 0, + "k": + { + "i": + [ + [ + 3.068, + 6.021 + ], + [ + 0, + 17.922 + ], + [ + 0, + 0 + ], + [ + -3.488, + 6.845 + ], + [ + -6.021, + 3.068 + ], + [ + -17.922, + 0 + ], + [ + 0, + 0 + ], + [ + -48.13, + -57.299 + ], + [ + 0, + 0 + ], + [ + 6.845, + 3.488 + ] + ], + "o": + [ + [ + -3.488, + -6.845 + ], + [ + 0, + 0 + ], + [ + 0, + -17.922 + ], + [ + 3.068, + -6.021 + ], + [ + 6.845, + -3.488 + ], + [ + 0, + 0 + ], + [ + 40.779, + 19.206 + ], + [ + 0, + 0 + ], + [ + -17.922, + 0 + ], + [ + -6.021, + -3.068 + ] + ], + "v": + [ + [ + -86.672, + 34.528 + ], + [ + -90.16, + 0.8 + ], + [ + -90.16, + -0.8 + ], + [ + -86.672, + -34.528 + ], + [ + -72.688, + -48.512 + ], + [ + -38.96, + -52 + ], + [ + -28.425, + -52 + ], + [ + 90.16, + 52 + ], + [ + -38.96, + 52 + ], + [ + -72.688, + 48.512 + ] + ], + "c": true + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 1, + 1, + 1, + 0.5, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0.64, + 0.5, + 0.32, + 1, + 0 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 51.107, + -52 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + -64.124, + 77.782 + ], + "ix": 6 + }, + "t": 1, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Wave Back", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Background-Gradient-Pink", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 155, + 52, + 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": + [ + 310, + 104 + ], + "ix": 2 + }, + "p": + { + "a": 0, + "k": + [ + 0, + 0 + ], + "ix": 3 + }, + "r": + { + "a": 0, + "k": 32, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0.069, + 0.96, + 0.754, + 0.712, + 0.535, + 0.96, + 0.684, + 0.781, + 1, + 0.96, + 0.613, + 0.85, + 0.069, + 1, + 0.535, + 0.5, + 1, + 0 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + 149, + -42.5 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 1.04, + 40.5 + ], + "ix": 6 + }, + "t": 2, + "h": + { + "a": 0, + "k": 14.188, + "ix": 7 + }, + "a": + { + "a": 0, + "k": 87.81, + "ix": 8 + }, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Background-Gradient-Pink", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Background-Gradient-Blue", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 155, + 52, + 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": + [ + 310, + 104 + ], + "ix": 2 + }, + "p": + { + "a": 0, + "k": + [ + 0, + 0 + ], + "ix": 3 + }, + "r": + { + "a": 0, + "k": 32, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "gf", + "o": + { + "a": 0, + "k": 100, + "ix": 10 + }, + "r": 1, + "bm": 0, + "g": + { + "p": 3, + "k": + { + "a": 0, + "k": + [ + 0, + 0.588, + 0.855, + 0.992, + 0.5, + 0.588, + 0.855, + 0.992, + 1, + 0.588, + 0.855, + 0.992, + 0, + 0.64, + 0.5, + 0.32, + 1, + 0 + ], + "ix": 9 + } + }, + "s": + { + "a": 0, + "k": + [ + -149.371, + -52 + ], + "ix": 5 + }, + "e": + { + "a": 0, + "k": + [ + 160.629, + -52 + ], + "ix": 6 + }, + "t": 2, + "h": + { + "a": 0, + "k": 0, + "ix": 7 + }, + "a": + { + "a": 0, + "k": 0, + "ix": 8 + }, + "nm": "Gradient Fill 1", + "mn": "ADBE Vector Graphic - G-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": "Background-Gradient-Blue", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Background-Base", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 155, + 52, + 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": + [ + 310, + 104 + ], + "ix": 2 + }, + "p": + { + "a": 0, + "k": + [ + 0, + 0 + ], + "ix": 3 + }, + "r": + { + "a": 0, + "k": 32, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": + { + "a": 0, + "k": + [ + 0.321568638086, + 0.686274528503, + 0.909803926945, + 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": "Background-Base", + "np": 2, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ] + } + ], + "layers": + [ + { + "ddd": 0, + "ind": 1, + "ty": 0, + "nm": "Pre-comp 1", + "refId": "comp_0", + "sr": 1, + "ks": + { + "o": + { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": + { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": + { + "a": 0, + "k": + [ + 77.5, + 26, + 0 + ], + "ix": 2, + "l": 2 + }, + "a": + { + "a": 0, + "k": + [ + 155, + 52, + 0 + ], + "ix": 1, + "l": 2 + }, + "s": + { + "a": 0, + "k": + [ + 50, + 50, + 100 + ], + "ix": 6, + "l": 2 + } + }, + "ao": 0, + "w": 310, + "h": 104, + "ip": 0, + "op": 455, + "st": 0, + "bm": 0 + } + ], + "markers": + [] +} diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/VideoPlayer/VideoPlayerView.swift b/DuckDuckGo/OnboardingExperiment/AddToDock/VideoPlayer/VideoPlayerView.swift new file mode 100644 index 0000000000..c840bb7555 --- /dev/null +++ b/DuckDuckGo/OnboardingExperiment/AddToDock/VideoPlayer/VideoPlayerView.swift @@ -0,0 +1,114 @@ +// +// VideoPlayerView.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 AVFoundation + +struct VideoPlayerView: View { + + @ObservedObject private var model: VideoPlayerViewModel + + private var isPlaying: Binding + + init(model: VideoPlayerViewModel, isPlaying: Binding = .constant(true)) { + self.model = model + self.isPlaying = isPlaying + } + + var body: some View { + PlayerView(player: model.player) + .foregroundColor(Color.red) + .onChange(of: isPlaying.wrappedValue) { newValue in + if newValue { + model.play() + } else { + model.pause() + } + } + } + +} + +// MARK: - Private + +// AVKit provides a SwiftUI view called VideoPlayer view to render AVPlayer. +// The issue is that is not possible to change the background/foreground color of the view so the default color is black. +// Using UIKit -> AVPlayerLayer solves the problem. +private struct PlayerView: UIViewRepresentable { + + private let player: AVPlayer + + init(player: AVPlayer) { + self.player = player + } + + func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext) { + } + + func makeUIView(context: Context) -> UIView { + return PlayerUIView(player: player) + } +} + +private final class PlayerUIView: UIView { + private let playerLayer = AVPlayerLayer() + + init(player: AVPlayer) { + playerLayer.player = player + super.init(frame: .zero) + layer.addSublayer(playerLayer) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + playerLayer.frame = bounds + } +} + +// MARK: - Preview + +struct VideoPlayerView_Previews: PreviewProvider { + + @MainActor + struct VideoPlayerPreview: View { + static let videoURL = Bundle.main.url(forResource: "add-to-dock-demo", withExtension: "mp4")! + @State var isPlaying = false + @State var model = VideoPlayerViewModel(url: Self.videoURL, loopVideo: true) + + var body: some View { + VideoPlayerView( + model: model, + isPlaying: $isPlaying + ) + .onAppear(perform: { + isPlaying = true + }) + } + } + + static var previews: some View { + VideoPlayerPreview() + .preferredColorScheme(.light) + } +} diff --git a/DuckDuckGo/OnboardingExperiment/AddToDock/VideoPlayer/VideoPlayerViewModel.swift b/DuckDuckGo/OnboardingExperiment/AddToDock/VideoPlayer/VideoPlayerViewModel.swift new file mode 100644 index 0000000000..81ff6a0dd7 --- /dev/null +++ b/DuckDuckGo/OnboardingExperiment/AddToDock/VideoPlayer/VideoPlayerViewModel.swift @@ -0,0 +1,77 @@ +// +// VideoPlayerViewModel.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 AVFoundation + +@MainActor +final class VideoPlayerViewModel: ObservableObject { + + @Published private(set) var player: AVPlayer + + let url: URL + + private var playerLooper: AVPlayerLooper? + + var isLoopingVideo: Bool { + playerLooper != nil + } + + init( + url: URL, + loopVideo: Bool, + player: AVQueuePlayer = AVQueuePlayer() + ) { + self.url = url + self.player = player + + configureVideoPlayer() + loadAsset(in: player, shouldLoopVideo: loopVideo) + } + + func play() { + player.play() + } + + func pause() { + player.pause() + } + +} + +// MARK: - Private + +private extension VideoPlayerViewModel { + + func configureVideoPlayer() { + // Let the application goes to sleep if needed when the video is playing. Set to false as we're not playing a movie. + // If in the future we have cases where these values need to be different we can inject a configuration. + player.preventsDisplaySleepDuringVideoPlayback = false + // Disable playback video on external displays. + player.allowsExternalPlayback = false + } + + func loadAsset(in player: AVQueuePlayer, shouldLoopVideo: Bool) { + let playerItem = AVPlayerItem(url: url) + player.replaceCurrentItem(with: playerItem) + if shouldLoopVideo { + playerLooper = AVPlayerLooper(player: player, templateItem: playerItem) + } + } + +} diff --git a/DuckDuckGo/OnboardingExperiment/AppIconPicker/AppIconPickerViewModel.swift b/DuckDuckGo/OnboardingExperiment/AppIconPicker/AppIconPickerViewModel.swift index ceaebee301..2310d99572 100644 --- a/DuckDuckGo/OnboardingExperiment/AppIconPicker/AppIconPickerViewModel.swift +++ b/DuckDuckGo/OnboardingExperiment/AppIconPicker/AppIconPickerViewModel.swift @@ -50,8 +50,11 @@ final class AppIconPickerViewModel: ObservableObject { } } -protocol AppIconManaging { +protocol AppIconProviding { var appIcon: AppIcon { get } +} + +protocol AppIconManaging: AppIconProviding { func changeAppIcon(_ appIcon: AppIcon, completionHandler: ((Error?) -> Void)?) } diff --git a/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/ContextualOnboardingDialogs.swift b/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/ContextualOnboardingDialogs.swift index 3a1633086f..f5bb5d47ec 100644 --- a/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/ContextualOnboardingDialogs.swift +++ b/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/ContextualOnboardingDialogs.swift @@ -185,44 +185,110 @@ struct OnboardingTrackersDoneDialog: View { struct OnboardingFinalDialog: View { let title = UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenTitle - let message: String let cta = UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenButton - - let highFiveAction: () -> Void + + let logoPosition: DaxDialogLogoPosition + let message: String + let canShowAddToDockTutorial: Bool + let dismissAction: (_ fromAddToDock: Bool) -> Void + + @State private var showAddToDockTutorial = false var body: some View { ScrollView(.vertical, showsIndicators: false) { - DaxDialogView(logoPosition: .left) { - ContextualDaxDialogContent( - title: title, - titleFont: Font(UIFont.daxTitle3()), - message: NSAttributedString(string: message), - customActionView: AnyView( - OnboardingCTAButton( - title: cta, - action: highFiveAction - ) + DaxDialogView(logoPosition: logoPosition) { + if showAddToDockTutorial { + OnboardingAddToDockTutorialContent { + dismissAction(true) + } + } else { + ContextualDaxDialogContent( + title: title, + titleFont: Font(UIFont.daxTitle3()), + message: NSAttributedString(string: message), + customView: AnyView(customView), + customActionView: AnyView(customActionView) ) - ) + } } .padding() } } + + @ViewBuilder + private var customView: some View { + if canShowAddToDockTutorial { + AddToDockPromoView() + .aspectRatio(contentMode: .fill) + .padding(.vertical) + } else { + EmptyView() + } + } + + @ViewBuilder + private var customActionView: some View { + VStack { + if canShowAddToDockTutorial { + OnboardingCTAButton( + title: UserText.AddToDockOnboarding.Buttons.addToDockTutorial, + action: { + showAddToDockTutorial = true + } + ) + } + OnboardingCTAButton( + title: cta, + buttonStyle: canShowAddToDockTutorial ? .ghost : .primary, + action: { + dismissAction(false) + } + ) + } + } } struct OnboardingCTAButton: View { + enum ButtonStyle { + case primary + case ghost + } + let title: String + var buttonStyle: ButtonStyle = .primary let action: () -> Void + var body: some View { - Button(action: action) { + let button = Button(action: action) { Text(title) } - .buttonStyle(PrimaryButtonStyle(compact: true)) + + switch buttonStyle { + case .primary: + button.buttonStyle(PrimaryButtonStyle(compact: true)) + case .ghost: + button.buttonStyle(GhostButtonStyle()) + } } } +struct OnboardingAddToDockTutorialContent: View { + let title = UserText.AddToDockOnboarding.Tutorial.title + let message = UserText.AddToDockOnboarding.Tutorial.message + let cta = UserText.AddToDockOnboarding.Buttons.dismiss + + let dismissAction: () -> Void + + var body: some View { + AddToDockTutorialView( + title: title, + message: message, + action: dismissAction) + } +} + // MARK: - Preview #Preview("Try Search") { @@ -252,9 +318,24 @@ struct OnboardingCTAButton: View { .padding() } -#Preview("Final Dialog") { - OnboardingFinalDialog(message: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage, highFiveAction: {}) - .padding() +#Preview("Final Dialog - No Add to Dock Tutorial") { + OnboardingFinalDialog( + logoPosition: .top, + message: UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage, + canShowAddToDockTutorial: false, + dismissAction: { _ in } + ) + .padding() +} + +#Preview("Final Dialog - Add to Dock Tutorial") { + OnboardingFinalDialog( + logoPosition: .left, + message: UserText.AddToDockOnboarding.EndOfJourney.message, + canShowAddToDockTutorial: true, + dismissAction: { _ in } + ) + .padding() } #Preview("Trackers Dialog") { @@ -270,3 +351,13 @@ struct OnboardingCTAButton: View { ) .padding() } + +#Preview("Add To Dock Tutorial - Light") { + OnboardingAddToDockTutorialContent(dismissAction: {}) + .preferredColorScheme(.light) +} + +#Preview("Add To Dock Tutorial - Dark") { + OnboardingAddToDockTutorialContent(dismissAction: {}) + .preferredColorScheme(.dark) +} diff --git a/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/NewTabDaxDialogFactory.swift b/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/NewTabDaxDialogFactory.swift index 6d99fc21d9..c4f74f8217 100644 --- a/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/NewTabDaxDialogFactory.swift +++ b/DuckDuckGo/OnboardingExperiment/ContextualDaxDialogs/NewTabDaxDialogFactory.swift @@ -30,7 +30,7 @@ final class NewTabDaxDialogFactory: NewTabDaxDialogProvider { private var delegate: OnboardingNavigationDelegate? private let contextualOnboardingLogic: ContextualOnboardingLogic private let onboardingPixelReporter: OnboardingPixelReporting - private let onboardingManager: OnboardingHighlightsManaging + private let onboardingManager: OnboardingHighlightsManaging & OnboardingAddToDockManaging private var gradientType: OnboardingGradientType { onboardingManager.isOnboardingHighlightsEnabled ? .highlights : .default @@ -40,7 +40,7 @@ final class NewTabDaxDialogFactory: NewTabDaxDialogProvider { delegate: OnboardingNavigationDelegate?, contextualOnboardingLogic: ContextualOnboardingLogic, onboardingPixelReporter: OnboardingPixelReporting, - onboardingManager: OnboardingHighlightsManaging = OnboardingManager() + onboardingManager: OnboardingHighlightsManaging & OnboardingAddToDockManaging = OnboardingManager() ) { self.delegate = delegate self.contextualOnboardingLogic = contextualOnboardingLogic @@ -99,14 +99,22 @@ final class NewTabDaxDialogFactory: NewTabDaxDialogProvider { } private func createFinalDialog(onDismiss: @escaping () -> Void) -> some View { - let message = onboardingManager.isOnboardingHighlightsEnabled ? UserText.HighlightsOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage : UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage + let message = if onboardingManager.isAddToDockEnabled { + UserText.AddToDockOnboarding.EndOfJourney.message + } else { + onboardingManager.isOnboardingHighlightsEnabled ? UserText.HighlightsOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage : UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage + } return FadeInView { - OnboardingFinalDialog(message: message, highFiveAction: { [weak self] in - self?.onboardingPixelReporter.trackEndOfJourneyDialogCTAAction() + OnboardingFinalDialog(logoPosition: .top, message: message, canShowAddToDockTutorial: onboardingManager.isAddToDockEnabled) { [weak self] isDismissedFromAddToDock in + if isDismissedFromAddToDock { + Logger.onboarding.debug("Dismissed from add to dock") + } else { + Logger.onboarding.debug("Dismissed from end of Journey") + self?.onboardingPixelReporter.trackEndOfJourneyDialogCTAAction() + } onDismiss() - }) - .onboardingDaxDialogStyle() + } } .onboardingContextualBackgroundStyle(background: .illustratedGradient(gradientType)) .onFirstAppear { [weak self] in diff --git a/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualDaxDialogsFactory.swift b/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualDaxDialogsFactory.swift index c1463df1a1..b6b9f08289 100644 --- a/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualDaxDialogsFactory.swift +++ b/DuckDuckGo/OnboardingExperiment/ContextualOnboarding/ContextualDaxDialogsFactory.swift @@ -48,7 +48,7 @@ final class ExperimentContextualDaxDialogsFactory: ContextualDaxDialogsFactory { private let contextualOnboardingSettings: ContextualOnboardingSettings private let contextualOnboardingPixelReporter: OnboardingPixelReporting private let contextualOnboardingSiteSuggestionsProvider: OnboardingSuggestionsItemsProviding - private let onboardingManager: OnboardingHighlightsManaging + private let onboardingManager: OnboardingHighlightsManaging & OnboardingAddToDockManaging private var gradientType: OnboardingGradientType { onboardingManager.isOnboardingHighlightsEnabled ? .highlights : .default @@ -59,7 +59,7 @@ final class ExperimentContextualDaxDialogsFactory: ContextualDaxDialogsFactory { contextualOnboardingSettings: ContextualOnboardingSettings = DefaultDaxDialogsSettings(), contextualOnboardingPixelReporter: OnboardingPixelReporting, contextualOnboardingSiteSuggestionsProvider: OnboardingSuggestionsItemsProviding = OnboardingSuggestedSitesProvider(surpriseItemTitle: UserText.DaxOnboardingExperiment.ContextualOnboarding.tryASearchOptionSurpriseMeTitle), - onboardingManager: OnboardingHighlightsManaging = OnboardingManager() + onboardingManager: OnboardingHighlightsManaging & OnboardingAddToDockManaging = OnboardingManager() ) { self.contextualOnboardingSettings = contextualOnboardingSettings self.contextualOnboardingLogic = contextualOnboardingLogic @@ -182,11 +182,20 @@ final class ExperimentContextualDaxDialogsFactory: ContextualDaxDialogsFactory { } private func endOfJourneyDialog(delegate: ContextualOnboardingDelegate, pixelName: Pixel.Event) -> some View { - let message = onboardingManager.isOnboardingHighlightsEnabled ? UserText.HighlightsOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage : UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage + let message = if onboardingManager.isAddToDockEnabled { + UserText.AddToDockOnboarding.EndOfJourney.message + } else { + onboardingManager.isOnboardingHighlightsEnabled ? UserText.HighlightsOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage : UserText.DaxOnboardingExperiment.ContextualOnboarding.onboardingFinalScreenMessage + } - return OnboardingFinalDialog(message: message, highFiveAction: { [weak delegate, weak self] in + return OnboardingFinalDialog(logoPosition: .left, message: message, canShowAddToDockTutorial: onboardingManager.isAddToDockEnabled, dismissAction: { [weak delegate, weak self] isDismissedFromAddToDock in delegate?.didTapDismissContextualOnboardingAction() - self?.contextualOnboardingPixelReporter.trackEndOfJourneyDialogCTAAction() + if isDismissedFromAddToDock { + Logger.onboarding.debug("Dismissed from add to dock") + } else { + Logger.onboarding.debug("Dismissed from end of Journey") + self?.contextualOnboardingPixelReporter.trackEndOfJourneyDialogCTAAction() + } }) .onFirstAppear { [weak self] in self?.contextualOnboardingLogic.setFinalOnboardingDialogSeen() diff --git a/DuckDuckGo/OnboardingExperiment/Manager/OnboardingManager.swift b/DuckDuckGo/OnboardingExperiment/Manager/OnboardingManager.swift index c504061045..f3ab2408a4 100644 --- a/DuckDuckGo/OnboardingExperiment/Manager/OnboardingManager.swift +++ b/DuckDuckGo/OnboardingExperiment/Manager/OnboardingManager.swift @@ -20,35 +20,44 @@ import BrowserServicesKit import Core -protocol OnboardingHighlightsManaging: AnyObject { - var isOnboardingHighlightsEnabled: Bool { get } -} - -protocol OnboardingHighlightsDebugging: OnboardingHighlightsManaging { - var isLocalFlagEnabled: Bool { get set } - var isFeatureFlagEnabled: Bool { get } -} - -final class OnboardingManager: OnboardingHighlightsManaging, OnboardingHighlightsDebugging { +final class OnboardingManager { private var appDefaults: AppDebugSettings private let featureFlagger: FeatureFlagger private let variantManager: VariantManager + private let isIphone: Bool init( appDefaults: AppDebugSettings = AppDependencyProvider.shared.appSettings, featureFlagger: FeatureFlagger = AppDependencyProvider.shared.featureFlagger, - variantManager: VariantManager = DefaultVariantManager() + variantManager: VariantManager = DefaultVariantManager(), + isIphone: Bool = UIDevice.current.userInterfaceIdiom == .phone ) { self.appDefaults = appDefaults self.featureFlagger = featureFlagger self.variantManager = variantManager + self.isIphone = isIphone } +} + +// MARK: - Onboarding Highlights + +protocol OnboardingHighlightsManaging: AnyObject { + var isOnboardingHighlightsEnabled: Bool { get } +} + +protocol OnboardingHighlightsDebugging: OnboardingHighlightsManaging { + var isOnboardingHighlightsLocalFlagEnabled: Bool { get set } + var isOnboardingHighlightsFeatureFlagEnabled: Bool { get } +} + + +extension OnboardingManager: OnboardingHighlightsManaging, OnboardingHighlightsDebugging { var isOnboardingHighlightsEnabled: Bool { - variantManager.isOnboardingHighlightsExperiment || (isLocalFlagEnabled && isFeatureFlagEnabled) + variantManager.isOnboardingHighlightsExperiment || (isOnboardingHighlightsLocalFlagEnabled && isOnboardingHighlightsFeatureFlagEnabled) } - var isLocalFlagEnabled: Bool { + var isOnboardingHighlightsLocalFlagEnabled: Bool { get { appDefaults.onboardingHighlightsEnabled } @@ -57,7 +66,41 @@ final class OnboardingManager: OnboardingHighlightsManaging, OnboardingHighlight } } - var isFeatureFlagEnabled: Bool { + var isOnboardingHighlightsFeatureFlagEnabled: Bool { featureFlagger.isFeatureOn(.onboardingHighlights) } + +} + +// MARK: - Add to Dock + +protocol OnboardingAddToDockManaging: AnyObject { + var isAddToDockEnabled: Bool { get } +} + +protocol OnboardingAddToDockDebugging: AnyObject { + var isAddToDockLocalFlagEnabled: Bool { get set } + var isAddToDockFeatureFlagEnabled: Bool { get } +} + +extension OnboardingManager: OnboardingAddToDockManaging, OnboardingAddToDockDebugging { + + var isAddToDockEnabled: Bool { + // TODO: Add variant condition once the experiment is setup + isIphone && isAddToDockLocalFlagEnabled && isAddToDockFeatureFlagEnabled + } + + var isAddToDockLocalFlagEnabled: Bool { + get { + appDefaults.onboardingAddToDockEnabled + } + set { + appDefaults.onboardingAddToDockEnabled = newValue + } + } + + var isAddToDockFeatureFlagEnabled: Bool { + featureFlagger.isFeatureOn(.onboardingAddToDock) + } + } diff --git a/DuckDuckGo/PrivacyInfo.xcprivacy b/DuckDuckGo/PrivacyInfo.xcprivacy index 15d10330da..98e99b1d0f 100644 --- a/DuckDuckGo/PrivacyInfo.xcprivacy +++ b/DuckDuckGo/PrivacyInfo.xcprivacy @@ -22,7 +22,7 @@ NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons - CA92.1 + 1C8F.1 diff --git a/DuckDuckGo/RootDebugViewController.swift b/DuckDuckGo/RootDebugViewController.swift index 23500ebf8c..0283c332dd 100644 --- a/DuckDuckGo/RootDebugViewController.swift +++ b/DuckDuckGo/RootDebugViewController.swift @@ -47,9 +47,7 @@ class RootDebugViewController: UITableViewController { case newTabPageSections = 674 case onboarding = 676 case resetSyncPromoPrompts = 677 - case resetDuckPlayerExperiment = 678 - case overrideDuckPlayerExperiment = 679 - case overrideDuckPlayerExperimentControl = 680 + case resetTipKit = 681 } @IBOutlet weak var shareButton: UIBarButtonItem! @@ -64,6 +62,7 @@ class RootDebugViewController: UITableViewController { private var sync: DDGSyncing? private var internalUserDecider: DefaultInternalUserDecider? var tabManager: TabManager? + private var tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling? @UserDefaultsWrapper(key: .lastConfigurationRefreshDate, defaultValue: .distantPast) private var lastConfigurationRefreshDate: Date @@ -72,24 +71,29 @@ class RootDebugViewController: UITableViewController { sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, - tabManager: TabManager) { + tabManager: TabManager, + tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling = TipKitDebugOptionsUIActionHandler()) { self.sync = sync self.bookmarksDatabase = bookmarksDatabase self.internalUserDecider = internalUserDecider as? DefaultInternalUserDecider self.tabManager = tabManager + self.tipKitUIActionHandler = tipKitUIActionHandler + + super.init(coder: coder) + } + + required init?(coder: NSCoder) { super.init(coder: coder) } - - func configure(sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, tabManager: TabManager) { + + func configure(sync: DDGSyncing, bookmarksDatabase: CoreDataDatabase, internalUserDecider: InternalUserDecider, tabManager: TabManager, tipKitUIActionHandler: TipKitDebugOptionsUIActionHandling = TipKitDebugOptionsUIActionHandler()) { + self.sync = sync self.bookmarksDatabase = bookmarksDatabase self.internalUserDecider = internalUserDecider as? DefaultInternalUserDecider self.tabManager = tabManager - } - - required init?(coder: NSCoder) { - super.init(coder: coder) + self.tipKitUIActionHandler = tipKitUIActionHandler } @IBSegueAction func onCreateImageCacheDebugScreen(_ coder: NSCoder) -> ImageCacheDebugViewController? { @@ -184,15 +188,8 @@ class RootDebugViewController: UITableViewController { let syncPromoPresenter = SyncPromoManager(syncService: sync) syncPromoPresenter.resetPromos() ActionMessageView.present(message: "Sync Promos reset") - case .resetDuckPlayerExperiment: - DuckPlayerLaunchExperiment().cleanup() - ActionMessageView.present(message: "Experiment Settings deleted. You'll be assigned a random cohort") - case .overrideDuckPlayerExperiment: - DuckPlayerLaunchExperiment().override() - ActionMessageView.present(message: "Overriding experiment. You are now in the 'experiment' group. Restart the app to complete") - case .overrideDuckPlayerExperimentControl: - DuckPlayerLaunchExperiment().override(control: true) - ActionMessageView.present(message: "Overriding experiment. You are now in the 'control' group. Restart the app to complete") + case .resetTipKit: + tipKitUIActionHandler?.resetTipKitTapped() } } } diff --git a/DuckDuckGo/Settings.bundle/Root.plist b/DuckDuckGo/Settings.bundle/Root.plist index 549ce4019a..a7f84961db 100644 --- a/DuckDuckGo/Settings.bundle/Root.plist +++ b/DuckDuckGo/Settings.bundle/Root.plist @@ -6,7 +6,7 @@ DefaultValue - 7.142.0 + 7.143.0 Key version Title diff --git a/DuckDuckGo/SettingsMainSettingsView.swift b/DuckDuckGo/SettingsMainSettingsView.swift index b851cda021..02b487031b 100644 --- a/DuckDuckGo/SettingsMainSettingsView.swift +++ b/DuckDuckGo/SettingsMainSettingsView.swift @@ -70,12 +70,10 @@ struct SettingsMainSettingsView: View { // Duck Player // We need to hide the settings until the user is enrolled in the experiment - if DuckPlayerLaunchExperiment().isEnrolled && DuckPlayerLaunchExperiment().isExperimentCohort { - if viewModel.isInternalUser || viewModel.state.duckPlayerEnabled { - NavigationLink(destination: SettingsDuckPlayerView().environmentObject(viewModel)) { - SettingsCellView(label: UserText.duckPlayerFeatureName, - image: Image("SettingsDuckPlayer")) - } + if viewModel.state.duckPlayerEnabled { + NavigationLink(destination: SettingsDuckPlayerView().environmentObject(viewModel)) { + SettingsCellView(label: UserText.duckPlayerFeatureName, + image: Image("SettingsDuckPlayer")) } } } diff --git a/DuckDuckGo/SimpleNewTabPageView.swift b/DuckDuckGo/SimpleNewTabPageView.swift new file mode 100644 index 0000000000..cf31697226 --- /dev/null +++ b/DuckDuckGo/SimpleNewTabPageView.swift @@ -0,0 +1,220 @@ +// +// SimpleNewTabPageView.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 +import RemoteMessaging + +struct SimpleNewTabPageView: View { + @Environment(\.horizontalSizeClass) var horizontalSizeClass + + @ObservedObject private var viewModel: NewTabPageViewModel + @ObservedObject private var messagesModel: NewTabPageMessagesModel + @ObservedObject private var favoritesViewModel: FavoritesViewModel + + init(viewModel: NewTabPageViewModel, + messagesModel: NewTabPageMessagesModel, + favoritesViewModel: FavoritesViewModel) { + self.viewModel = viewModel + self.messagesModel = messagesModel + self.favoritesViewModel = favoritesViewModel + + self.messagesModel.load() + } + + private var isShowingSections: Bool { + !favoritesViewModel.allFavorites.isEmpty + } + + var body: some View { + if !viewModel.isOnboarding { + mainView + .background(Color(designSystemColor: .background)) + .simultaneousGesture( + DragGesture() + .onChanged({ value in + if value.translation.height > 0 { + viewModel.beginDragging() + } + }) + .onEnded({ _ in viewModel.endDragging() }) + ) + } + } + + @ViewBuilder + private var mainView: some View { + if isShowingSections { + sectionsView + } else { + emptyStateView + } + } +} + +private extension SimpleNewTabPageView { + // MARK: - Views + @ViewBuilder + private var sectionsView: some View { + GeometryReader { proxy in + ScrollView { + VStack(spacing: Metrics.sectionSpacing) { + + messagesSectionView + .padding(.top, Metrics.nonGridSectionTopPadding) + + favoritesSectionView(proxy: proxy) + } + .padding(Metrics.largePadding) + } + .withScrollKeyboardDismiss() + } + } + + @ViewBuilder + private var emptyStateView: some View { + ZStack { + NewTabPageDaxLogoView() + + VStack(spacing: Metrics.sectionSpacing) { + messagesSectionView + .padding(.top, Metrics.nonGridSectionTopPadding) + .frame(maxHeight: .infinity, alignment: .top) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + } + .padding(Metrics.largePadding) + } + + private var messagesSectionView: some View { + ForEach(messagesModel.homeMessageViewModels, id: \.messageId) { messageModel in + HomeMessageView(viewModel: messageModel) + .frame(maxWidth: horizontalSizeClass == .regular ? Metrics.messageMaximumWidthPad : Metrics.messageMaximumWidth) + .transition(.scale.combined(with: .opacity)) + } + } + + private func favoritesSectionView(proxy: GeometryProxy) -> some View { + FavoritesView(model: favoritesViewModel, + isAddingFavorite: .constant(false), + geometry: proxy) + } +} + +private extension View { + @ViewBuilder + func withScrollKeyboardDismiss() -> some View { + if #available(iOS 16, *) { + scrollDismissesKeyboard(.immediately) + } else { + self + } + } +} + +private struct Metrics { + + static let regularPadding = 16.0 + static let largePadding = 24.0 + static let sectionSpacing = 32.0 + static let nonGridSectionTopPadding = -8.0 + + static let messageMaximumWidth: CGFloat = 380 + static let messageMaximumWidthPad: CGFloat = 455 +} + +// MARK: - Preview + +#Preview("Regular") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [] + ) + ), + favoritesViewModel: FavoritesPreviewModel() + ) +} + +#Preview("With message") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [ + HomeMessage.remoteMessage( + remoteMessage: RemoteMessageModel( + id: "0", + content: .small(titleText: "Title", descriptionText: "Description"), + matchingRules: [], + exclusionRules: [], + isMetricsEnabled: false + ) + ) + ] + ) + ), + favoritesViewModel: FavoritesPreviewModel() + ) +} + +#Preview("No favorites") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [] + ) + ), + favoritesViewModel: FavoritesPreviewModel(favorites: []) + ) +} + +#Preview("Empty") { + SimpleNewTabPageView( + viewModel: NewTabPageViewModel(), + messagesModel: NewTabPageMessagesModel( + homePageMessagesConfiguration: PreviewMessagesConfiguration( + homeMessages: [] + ) + ), + favoritesViewModel: FavoritesPreviewModel() + ) +} + +private final class PreviewMessagesConfiguration: HomePageMessagesConfiguration { + private(set) var homeMessages: [HomeMessage] + + init(homeMessages: [HomeMessage]) { + self.homeMessages = homeMessages + } + + func refresh() { + + } + + func didAppear(_ homeMessage: HomeMessage) { + // no-op + } + + func dismissHomeMessage(_ homeMessage: HomeMessage) { + homeMessages = homeMessages.dropLast() + } +} diff --git a/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift b/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift new file mode 100644 index 0000000000..0ec0ee2ef9 --- /dev/null +++ b/DuckDuckGo/Subscription/SubscriptionCookieManageEventPixelMapping.swift @@ -0,0 +1,50 @@ +// +// SubscriptionCookieManageEventPixelMapping.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 +import Subscription + +public final class SubscriptionCookieManageEventPixelMapping: EventMapping { + + public init() { + super.init { event, _, _, _ in + let pixel: Pixel.Event = { + switch event { + case .errorHandlingAccountDidSignInTokenIsMissing: + return .privacyProSubscriptionCookieMissingTokenOnSignIn + case .subscriptionCookieRefreshedWithAccessToken: + return .privacyProSubscriptionCookieRefreshedWithAccessToken + case .subscriptionCookieRefreshedWithEmptyValue: + return .privacyProSubscriptionCookieRefreshedWithEmptyValue + case .failedToSetSubscriptionCookie: + return .privacyProSubscriptionCookieFailedToSetSubscriptionCookie + } + }() + + Pixel.fire(pixel: pixel) + + } + } + + override init(mapping: @escaping EventMapping.Mapping) { + fatalError("Use init()") + } +} diff --git a/DuckDuckGo/SwipeTabsCoordinator.swift b/DuckDuckGo/SwipeTabsCoordinator.swift index ea883d3dd7..8071d83ec9 100644 --- a/DuckDuckGo/SwipeTabsCoordinator.swift +++ b/DuckDuckGo/SwipeTabsCoordinator.swift @@ -314,8 +314,7 @@ extension SwipeTabsCoordinator: UICollectionViewDataSource { cell.omniBar = omniBar cell.omniBar?.translatesAutoresizingMaskIntoConstraints = false - cell.updateConstraints() - + cell.omniBar?.showSeparator() if self.appSettings.currentAddressBarPosition.isBottom { cell.omniBar?.moveSeparatorToTop() @@ -330,7 +329,9 @@ extension SwipeTabsCoordinator: UICollectionViewDataSource { } } - + + cell.setNeedsUpdateConstraints() + return cell } @@ -343,7 +344,7 @@ class OmniBarCell: UICollectionViewCell { subviews.forEach { $0.removeFromSuperview() } if let omniBar { addSubview(omniBar) - + NSLayoutConstraint.activate([ constrainView(omniBar, by: .leadingMargin), constrainView(omniBar, by: .trailingMargin), @@ -354,14 +355,14 @@ class OmniBarCell: UICollectionViewCell { } } } - + override func updateConstraints() { - super.updateConstraints() let left = superview?.safeAreaInsets.left ?? 0 let right = superview?.safeAreaInsets.right ?? 0 omniBar?.updateOmniBarPadding(left: left, right: right) + + super.updateConstraints() } - } extension TabsModel { diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index 24077c9454..46efdc1369 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -24,6 +24,7 @@ import WebKit import BrowserServicesKit import Persistence import History +import Subscription import os.log class TabManager { @@ -36,11 +37,12 @@ class TabManager { private let historyManager: HistoryManaging private let syncService: DDGSyncing private var previewsSource: TabPreviewsSource - private var duckPlayer: DuckPlayerProtocol + private var duckPlayer: DuckPlayerControlling private var privacyProDataReporter: PrivacyProDataReporting private let contextualOnboardingPresenter: ContextualOnboardingPresenting private let contextualOnboardingLogic: ContextualOnboardingLogic private let onboardingPixelReporter: OnboardingPixelReporting + private let subscriptionCookieManager: SubscriptionCookieManaging weak var delegate: TabDelegate? @@ -57,7 +59,8 @@ class TabManager { privacyProDataReporter: PrivacyProDataReporting, contextualOnboardingPresenter: ContextualOnboardingPresenting, contextualOnboardingLogic: ContextualOnboardingLogic, - onboardingPixelReporter: OnboardingPixelReporting) { + onboardingPixelReporter: OnboardingPixelReporting, + subscriptionCookieManager: SubscriptionCookieManaging) { self.model = model self.previewsSource = previewsSource self.bookmarksDatabase = bookmarksDatabase @@ -68,6 +71,7 @@ class TabManager { self.contextualOnboardingPresenter = contextualOnboardingPresenter self.contextualOnboardingLogic = contextualOnboardingLogic self.onboardingPixelReporter = onboardingPixelReporter + self.subscriptionCookieManager = subscriptionCookieManager registerForNotifications() } @@ -89,7 +93,8 @@ class TabManager { contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, - featureFlagger: AppDependencyProvider.shared.featureFlagger) + featureFlagger: AppDependencyProvider.shared.featureFlagger, + subscriptionCookieManager: subscriptionCookieManager) controller.applyInheritedAttribution(inheritedAttribution) controller.attachWebView(configuration: configuration, andLoadRequest: url == nil ? nil : URLRequest.userInitiated(url!), @@ -167,7 +172,8 @@ class TabManager { contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, - featureFlagger: AppDependencyProvider.shared.featureFlagger) + featureFlagger: AppDependencyProvider.shared.featureFlagger, + subscriptionCookieManager: subscriptionCookieManager) controller.attachWebView(configuration: configCopy, andLoadRequest: request, consumeCookies: !model.hasActiveTabs, diff --git a/DuckDuckGo/TabSwitcherTransition.swift b/DuckDuckGo/TabSwitcherTransition.swift index 5aebcad997..ea098cd149 100644 --- a/DuckDuckGo/TabSwitcherTransition.swift +++ b/DuckDuckGo/TabSwitcherTransition.swift @@ -86,7 +86,7 @@ class TabSwitcherTransitionDelegate: NSObject, UIViewControllerTransitioningDele return nil } - if mainVC.homeController != nil { + if mainVC.newTabPageViewController != nil { return FromHomeScreenTransition(mainViewController: mainVC, tabSwitcherViewController: tabSwitcherVC) } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 4ba66b9744..94a89dec38 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -41,6 +41,7 @@ import NetworkProtection import Onboarding import os.log import Navigation +import Subscription class TabViewController: UIViewController { @@ -92,6 +93,7 @@ class TabViewController: UIViewController { let appSettings: AppSettings var featureFlagger: FeatureFlagger + let subscriptionCookieManager: SubscriptionCookieManaging private lazy var internalUserDecider = AppDependencyProvider.shared.internalUserDecider private lazy var autofillNeverPromptWebsitesManager = AppDependencyProvider.shared.autofillNeverPromptWebsitesManager @@ -315,13 +317,14 @@ class TabViewController: UIViewController { bookmarksDatabase: CoreDataDatabase, historyManager: HistoryManaging, syncService: DDGSyncing, - duckPlayer: DuckPlayerProtocol?, + duckPlayer: DuckPlayerControlling?, privacyProDataReporter: PrivacyProDataReporting, contextualOnboardingPresenter: ContextualOnboardingPresenting, contextualOnboardingLogic: ContextualOnboardingLogic, onboardingPixelReporter: OnboardingCustomInteractionPixelReporting, urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(), - featureFlagger: FeatureFlagger) -> TabViewController { + featureFlagger: FeatureFlagger, + subscriptionCookieManager: SubscriptionCookieManaging) -> TabViewController { let storyboard = UIStoryboard(name: "Tab", bundle: nil) let controller = storyboard.instantiateViewController(identifier: "TabViewController", creator: { coder in TabViewController(coder: coder, @@ -336,7 +339,8 @@ class TabViewController: UIViewController { contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, urlCredentialCreator: urlCredentialCreator, - featureFlagger: featureFlagger + featureFlagger: featureFlagger, + subscriptionCookieManager: subscriptionCookieManager ) }) return controller @@ -349,7 +353,7 @@ class TabViewController: UIViewController { let historyManager: HistoryManaging let historyCapture: HistoryCapture - weak var duckPlayer: DuckPlayerProtocol? + weak var duckPlayer: DuckPlayerControlling? var duckPlayerNavigationHandler: DuckPlayerNavigationHandling? let contextualOnboardingPresenter: ContextualOnboardingPresenting @@ -363,13 +367,14 @@ class TabViewController: UIViewController { historyManager: HistoryManaging, syncService: DDGSyncing, certificateTrustEvaluator: CertificateTrustEvaluating = CertificateTrustEvaluator(), - duckPlayer: DuckPlayerProtocol?, + duckPlayer: DuckPlayerControlling?, privacyProDataReporter: PrivacyProDataReporting, contextualOnboardingPresenter: ContextualOnboardingPresenting, contextualOnboardingLogic: ContextualOnboardingLogic, onboardingPixelReporter: OnboardingCustomInteractionPixelReporting, urlCredentialCreator: URLCredentialCreating = URLCredentialCreator(), - featureFlagger: FeatureFlagger) { + featureFlagger: FeatureFlagger, + subscriptionCookieManager: SubscriptionCookieManaging) { self.tabModel = tabModel self.appSettings = appSettings self.bookmarksDatabase = bookmarksDatabase @@ -388,7 +393,11 @@ class TabViewController: UIViewController { self.onboardingPixelReporter = onboardingPixelReporter self.urlCredentialCreator = urlCredentialCreator self.featureFlagger = featureFlagger + self.subscriptionCookieManager = subscriptionCookieManager super.init(coder: aDecoder) + + // Assign itself as tabNavigationHandler for DuckPlayer + duckPlayerNavigationHandler?.tabNavigationHandler = self } required init?(coder aDecoder: NSCoder) { @@ -636,6 +645,8 @@ class TabViewController: UIViewController { await webView.configuration.websiteDataStore.dataRecords(ofTypes: WKWebsiteDataStore.allWebsiteDataTypes()) let cookieStore = webView.configuration.websiteDataStore.httpCookieStore await WebCacheManager.shared.consumeCookies(httpCookieStore: cookieStore) + subscriptionCookieManager.resetLastRefreshDate() + await subscriptionCookieManager.refreshSubscriptionCookie() doLoad() } } @@ -728,7 +739,12 @@ class TabViewController: UIViewController { progressWorker.progressDidChange(webView.estimatedProgress) case #keyPath(WKWebView.url): - webViewUrlHasChanged() + // A short delay is required here, because the URL takes some time + // to propagate to the webView.url property accessor and might not + // be immediately available in the observer + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in + self?.webViewUrlHasChanged() + } case #keyPath(WKWebView.canGoBack): delegate?.tabLoadingStateDidChange(tab: self) @@ -745,37 +761,17 @@ class TabViewController: UIViewController { } func webViewUrlHasChanged() { + + // Handle DuckPlayer Navigation URL changes + if let handler = duckPlayerNavigationHandler, + let currentURL = webView.url { + _ = handler.handleURLChange(webView: webView) + } + if url == nil { url = webView.url } else if let currentHost = url?.host, let newHost = webView.url?.host, currentHost == newHost { url = webView.url - - // decideForPolicy is not called for JS navigation - // This ensures DuckPlayer works on internal JS navigation based on - // URL Changes - - if let url, - url.isYoutubeVideo { - - duckPlayerNavigationHandler?.handleEvent(event: .youtubeVideoPageVisited, - url: url, - navigationAction: nil) - - if duckPlayerNavigationHandler?.duckPlayer.settings.mode == .enabled { - duckPlayerNavigationHandler?.handleJSNavigation(url: url, webView: webView) - } - } - - - } - if let url { - duckPlayerNavigationHandler?.referrer = url.isYoutube ? .youtube : .other - - // Open in new tab if required - // If the lastRenderedURL is nil, it means we're already in a new tab - if webView.url != nil && lastRenderedURL != nil { - duckPlayerNavigationHandler?.handleEvent(event: .JSTriggeredNavigation, url: webView.url, navigationAction: nil) - } } } @@ -832,7 +828,7 @@ class TabViewController: UIViewController { public func reload() { updateContentMode() cachedRuntimeConfigurationForDomain = [:] - if let url = webView.url, url.isDuckPlayer { + if let handler = duckPlayerNavigationHandler { duckPlayerNavigationHandler?.handleReload(webView: webView) } else { webView.reload() @@ -1317,6 +1313,8 @@ extension TabViewController: WKNavigationDelegate { SKStoreReviewController.requestReview(in: scene) appRatingPrompt.shown() } + + duckPlayerNavigationHandler?.handleDidStartLoading(webView: webView) } func webView(_ webView: WKWebView, @@ -1496,6 +1494,11 @@ extension TabViewController: WKNavigationDelegate { daxDialogsDebouncer.debounce(for: 0.8) { [weak self] in self?.showDaxDialogOrStartTrackerNetworksAnimationIfNeeded() } + + // DuckPlayer finish loading actions + if let handler = duckPlayerNavigationHandler { + handler.handleDidFinishLoading(webView: webView) + } Task { @MainActor in if await webView.isCurrentSiteReferredFromDuckDuckGo { @@ -1706,19 +1709,6 @@ extension TabViewController: WKNavigationDelegate { decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - if #available(iOS 17.4, *), - navigationAction.request.url?.scheme == "marketplace-kit", - internalUserDecider.isInternalUser { - - decisionHandler(.allow) - let urlString = navigationAction.request.url?.absoluteString ?? "" - ActionMessageView.present(message: "Marketplace Kit URL detected", - actionTitle: "COPY", - presentationLocation: .withoutBottomBar, onAction: { - UIPasteboard.general.string = urlString - }) - return - } if let url = navigationAction.request.url { if !tabURLInterceptor.allowsNavigatingTo(url: url) { @@ -1733,6 +1723,15 @@ extension TabViewController: WKNavigationDelegate { } } + // Ask DuckPlayer to handle navigation if possible + if let handler = duckPlayerNavigationHandler { + + if handler.handleDelegateNavigation(navigationAction: navigationAction, webView: webView) { + decisionHandler(.cancel) + return + } + } + if let url = navigationAction.request.url, !url.isDuckDuckGoSearch, true == shouldWaitUntilContentBlockingIsLoaded({ [weak self, webView /* decision handler must be called */] in @@ -1829,9 +1828,6 @@ extension TabViewController: WKNavigationDelegate { if url.isDuckDuckGoSearch { StatisticsLoader.shared.refreshSearchRetentionAtb() privacyProDataReporter.saveSearchCount() - - // Duck Player Search Experiment - DuckPlayerLaunchExperiment(duckPlayerMode: duckPlayer?.settings.mode).fireSearchPixels() } self.delegate?.closeFindInPage(tab: self) @@ -1888,27 +1884,14 @@ extension TabViewController: WKNavigationDelegate { if navigationAction.isTargetingMainFrame(), navigationAction.navigationType == .backForward { adClickAttributionLogic.onBackForwardNavigation(mainFrameURL: webView.url) } - - if navigationAction.isTargetingMainFrame(), - url.isYoutubeVideo { - - duckPlayerNavigationHandler?.handleEvent(event: .youtubeVideoPageVisited, - url: url, - navigationAction: navigationAction) - - // Handle decidePolicy For - if duckPlayerNavigationHandler?.duckPlayer.settings.mode == .enabled { - duckPlayerNavigationHandler?.handleDecidePolicyFor(navigationAction, - completion: completion, - webView: webView) - return - } - - } let schemeType = SchemeHandler.schemeType(for: url) self.blobDownloadTargetFrame = nil switch schemeType { + case .allow: + completion(.allow) + return + case .navigational: performNavigationFor(url: url, navigationAction: navigationAction, @@ -1923,18 +1906,11 @@ extension TabViewController: WKNavigationDelegate { performBlobNavigation(navigationAction, completion: completion) case .duck: - duckPlayerNavigationHandler?.handleEvent(event: .youtubeVideoPageVisited, - url: url, - navigationAction: navigationAction) - - // Validate Duck Player setting to open in new tab or locally - if duckPlayerNavigationHandler?.shouldOpenInNewTab(navigationAction, webView: webView) ?? false { - delegate?.tab(self, didRequestNewTabForUrl: url, openedByPage: false, inheritingAttribution: nil) - } else { - duckPlayerNavigationHandler?.handleNavigation(navigationAction, webView: webView) + if navigationAction.isTargetingMainFrame() { + duckPlayerNavigationHandler?.handleDuckNavigation(navigationAction, webView: webView) + completion(.cancel) + return } - completion(.cancel) - return case .unknown: if navigationAction.navigationType == .linkActivated { @@ -2072,6 +2048,7 @@ extension TabViewController: WKNavigationDelegate { if !(error.failedUrl?.isCustomURLScheme() ?? false) { url = error.failedUrl showError(message: error.localizedDescription) + Pixel.fire(pixel: .webViewErrorPageShown) } webpageDidFailToLoad() @@ -2187,7 +2164,7 @@ extension TabViewController { return } - if self.shouldTriggerDownloadAction(for: navigationResponse) { + if self.shouldTriggerDownloadAction(for: navigationResponse) && !FilePreviewHelper.canAutoPreviewMIMEType(downloadMetadata.mimeType) { // Show alert to the file download self.presentSaveToDownloadsAlert(with: downloadMetadata) { callback(self.transfer(download, @@ -3133,3 +3110,23 @@ extension TabViewController: SpecialErrorPageUserScriptDelegate { } } + +// This Protocol allows DuckPlayerHandler access tabs +extension TabViewController: DuckPlayerTabNavigationHandling { + + func openTab(for url: URL) { + delegate?.tab(self, + didRequestNewTabForUrl: url, + openedByPage: true, + inheritingAttribution: adClickAttributionLogic.state) + + } + + func closeTab() { + if openingTab != nil { + delegate?.tabDidRequestClose(self) + return + } + } + +} diff --git a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift index 12ef1e53ee..8e8d715052 100644 --- a/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerBrowsingMenuExtension.swift @@ -84,12 +84,14 @@ extension TabViewController { entries.append(self.buildToggleProtectionEntry(forDomain: domain)) } - let name = UserText.actionReportBrokenSite - entries.append(BrowsingMenuEntry.regular(name: name, - image: UIImage(named: "Feedback-16")!, - action: { [weak self] in - self?.onReportBrokenSiteAction() - })) + if link != nil { + let name = UserText.actionReportBrokenSite + entries.append(BrowsingMenuEntry.regular(name: name, + image: UIImage(named: "Feedback-16")!, + action: { [weak self] in + self?.onReportBrokenSiteAction() + })) + } entries.append(.separator) diff --git a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift index 38770be4e7..8de6d3de6c 100644 --- a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift @@ -111,7 +111,8 @@ extension TabViewController { contextualOnboardingPresenter: contextualOnboardingPresenter, contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: onboardingPixelReporter, - featureFlagger: AppDependencyProvider.shared.featureFlagger) + featureFlagger: AppDependencyProvider.shared.featureFlagger, + subscriptionCookieManager: subscriptionCookieManager) tabController.isLinkPreview = true let configuration = WKWebViewConfiguration.nonPersistent() tabController.attachWebView(configuration: configuration, andLoadRequest: URLRequest.userInitiated(url), consumeCookies: false) diff --git a/DuckDuckGo/TipKit/Logger+TipKit.swift b/DuckDuckGo/TipKit/Logger+TipKit.swift new file mode 100644 index 0000000000..1d791692b4 --- /dev/null +++ b/DuckDuckGo/TipKit/Logger+TipKit.swift @@ -0,0 +1,28 @@ +// +// Logger+TipKit.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 os.log + +extension Logger { + + static var tipKit: Logger = { + Logger(subsystem: Bundle.main.bundleIdentifier ?? "DuckDuckGo", category: "TipKit") + }() +} diff --git a/DuckDuckGo/TipKit/TipKitAppEventHandling.swift b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift new file mode 100644 index 0000000000..572427579a --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitAppEventHandling.swift @@ -0,0 +1,50 @@ +// +// TipKitAppEventHandling.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 +import os.log + +protocol TipKitAppEventHandling { + func appDidFinishLaunching() +} + +struct TipKitAppEventHandler: TipKitAppEventHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController = .make(), + logger: Logger = .tipKit) { + + self.controller = controller + self.logger = logger + } + + func appDidFinishLaunching() { + if #available(iOS 17.0, *) { + controller.configureTipKit([ + .displayFrequency(.immediate), + .datastoreLocation(.applicationDefault) + ]) + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift new file mode 100644 index 0000000000..8b39a4ea95 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController+ConvenienceInitializers.swift @@ -0,0 +1,30 @@ +// +// TipKitController+ConvenienceInitializers.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 os + +extension TipKitController { + + static func make(logger: Logger = .tipKit, + userDefaults: UserDefaults = .standard) -> Self { + + self.init(logger: logger, userDefaults: userDefaults) + } +} diff --git a/DuckDuckGo/TipKit/TipKitController.swift b/DuckDuckGo/TipKit/TipKitController.swift new file mode 100644 index 0000000000..c91a0b534f --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitController.swift @@ -0,0 +1,93 @@ +// +// TipKitController.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 os.log +import TipKit + +protocol TipKitControlling { + @available(iOS 17.0, *) + func configureTipKit() + + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() +} + +final class TipKitController { + + private let logger: Logger + private let userDefaults: UserDefaults + + private var resetTipKitOnNextLaunch: Bool { + get { + userDefaults.bool(forKey: "resetTipKitOnNextLaunch") + } + + set { + userDefaults.set(newValue, forKey: "resetTipKitOnNextLaunch") + } + } + + init(logger: Logger, + userDefaults: UserDefaults) { + + self.logger = logger + self.userDefaults = userDefaults + } + + @available(iOS 17.0, macOS 14.0, *) + func configureTipKit(_ configuration: [Tips.ConfigurationOption] = []) { + do { + if resetTipKitOnNextLaunch { + resetTipKit() + resetTipKitOnNextLaunch = false + } + + try Tips.configure(configuration) + + logger.debug("TipKit initialized") + } catch { + logger.error("Failed to initialize TipKit: \(error)") + } + } + + @available(iOS 17.0, macOS 14.0, *) + private func resetTipKit() { + do { + try Tips.resetDatastore() + + logger.debug("TipKit reset") + } catch { + logger.debug("Failed to reset TipKit: \(error)") + } + } + + /// Resets TipKit + /// + /// One thing that's not documented as of 2024-10-09 is that resetting TipKit must happen before it's configured. + /// When trying to reset it after it's configured we get `TipKit.TipKitError(value: TipKit.TipKitError.Value.tipsDatastoreAlreadyConfigured)`. + /// In order to make things work for us we set a user defaults value that ensures TipKit will be reset on next + /// app launch instead of directly trying to reset it here. + /// + @available(iOS 17.0, *) + func resetTipKitOnNextAppLaunch() { + resetTipKitOnNextLaunch = true + logger.debug("TipKit will reset on next app launch") + } +} diff --git a/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift new file mode 100644 index 0000000000..1a12054a98 --- /dev/null +++ b/DuckDuckGo/TipKit/TipKitDebugOptionsUIActionHandling.swift @@ -0,0 +1,49 @@ +// +// TipKitDebugOptionsUIActionHandling.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 os.log + +protocol TipKitDebugOptionsUIActionHandling { + /// Resets TipKit + func resetTipKitTapped() +} + +struct TipKitDebugOptionsUIActionHandler: TipKitDebugOptionsUIActionHandling { + + private let controller: TipKitController + private let logger: Logger + + init(controller: TipKitController = .make(), + logger: Logger = .tipKit) { + + self.controller = controller + self.logger = logger + } + + func resetTipKitTapped() { + if #available(iOS 17.0, *) { + controller.resetTipKitOnNextAppLaunch() + + ActionMessageView.present(message: "TipKit will reset on next app launch.") + } else { + logger.log("TipKit initialization skipped: iOS 17.0 or later is required.") + } + } +} diff --git a/DuckDuckGo/UserScripts.swift b/DuckDuckGo/UserScripts.swift index 4b003e0a57..144686acb9 100644 --- a/DuckDuckGo/UserScripts.swift +++ b/DuckDuckGo/UserScripts.swift @@ -36,7 +36,7 @@ final class UserScripts: UserScriptsProvider { let autoconsentUserScript: AutoconsentUserScript var specialPages: SpecialPagesUserScript? - var duckPlayer: DuckPlayerProtocol? { + var duckPlayer: DuckPlayerControlling? { didSet { initializeDuckPlayer() } diff --git a/DuckDuckGo/UserText.swift b/DuckDuckGo/UserText.swift index b8fd72171c..331dc45c98 100644 --- a/DuckDuckGo/UserText.swift +++ b/DuckDuckGo/UserText.swift @@ -1275,7 +1275,7 @@ But if you *do* want a peek under the hood, you can find more information about public static let duckPlayerAskLabel = NSLocalizedString("duckPlayer.ask.label", value: "Ask every time", comment: "Text displayed when DuckPlayer is in 'Ask' mode.") public static let duckPlayerDisabledLabel = NSLocalizedString("duckPlayer.never.label", value: "Never", comment: "Text displayed when DuckPlayer is in off.") - public static let settingsOpenVideosInDuckPlayerLabel = NSLocalizedString("duckplayer.settings.open-videos-in", value: "Open Videos in Duck Player", comment: "Settings screen cell text for DuckPlayer settings") + public static let settingsOpenVideosInDuckPlayerLabel = NSLocalizedString("duckplayer.settings.open-videos-in", value: "Open YouTube Videos in Duck Player", comment: "Settings screen cell text for DuckPlayer settings") public static let duckPlayerFeatureName = NSLocalizedString("duckplayer.settings.title", value: "Duck Player", comment: "Settings screen cell text for DuckPlayer settings") public static let settingsOpenDuckPlayerNewTabLabel = NSLocalizedString("duckplayer.settings.open-new-tab-label", value: "Open Duck Player in a new tab", comment: "Settings screen cell text for DuckPlayer settings to open in new tab") @@ -1412,4 +1412,20 @@ But if you *do* want a peek under the hood, you can find more information about public static let skip = NSLocalizedString("onboarding.highlights.fireDialog.cta.skip", value: "Skip", comment: "The title of the fire button CTA to skip erasing the data.") } } + + public enum AddToDockOnboarding { + public enum Buttons { + static let addToDockTutorial = NSLocalizedString("contextual.onboarding.addToDock.buttons.tutorial", value: "Show Me How", comment: "Button at the end of the browser onboarding. On click it shows the user a video about how to add the application to the device dock.") + static let dismiss = NSLocalizedString("contextual.onboarding.addToDock.buttons.dismiss", value: "Start Browsing", comment: "Button on the last screen of the onboarding, it will dismiss the onboarding screen.") + } + + public enum EndOfJourney { + static let message = NSLocalizedString("contextual.onboarding.addToDock.endOfJourney.message", value: "Remember, every time you browse with me a creepy ad loses its wings.\n\nSo keep me in your Dock for daily browsing.", comment: "Message of the last screen of the onboarding to the browser app.") + } + + public enum Tutorial { + static let title = NSLocalizedString("contextual.onboarding.addToDock.tutorial.title", value: "Adding me to your Dock is easy.", comment: "The title of the onboarding dialog popup that explains how to add the DDG browser icon to the dock.") + static let message = NSLocalizedString("contextual.onboarding.addToDock.tutorial.message", value: "Find or search for the DuckDuckGo icon on your home screen. Then press and drag into place. That’s it!", comment: "The message of the onboarding dialog popup that explains how to add the DDG browser icon to the dock.") + } + } } diff --git a/DuckDuckGo/VPN.xcassets/Contents.json b/DuckDuckGo/VPN.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..1360a4b9fb --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Widget-Add-32 2.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32 2.pdf b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32 2.pdf new file mode 100644 index 0000000000..be3e56356e Binary files /dev/null and b/DuckDuckGo/VPN.xcassets/VPNAddWidgetTipIcon.imageset/Widget-Add-32 2.pdf differ diff --git a/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..cb612d2e7d --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Location-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Location-32.pdf b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Location-32.pdf new file mode 100644 index 0000000000..25b89dda77 Binary files /dev/null and b/DuckDuckGo/VPN.xcassets/VPNChangeLocationTipIcon.imageset/Location-32.pdf differ diff --git a/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json new file mode 100644 index 0000000000..492a76420b --- /dev/null +++ b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Moon-Snooze-32.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-Snooze-32.pdf b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-Snooze-32.pdf new file mode 100644 index 0000000000..60ce9c4901 Binary files /dev/null and b/DuckDuckGo/VPN.xcassets/VPNUseSnoozeTipIcon.imageset/Moon-Snooze-32.pdf differ diff --git a/DuckDuckGo/VPNAddWidgetTip.swift b/DuckDuckGo/VPNAddWidgetTip.swift new file mode 100644 index 0000000000..fd6f652415 --- /dev/null +++ b/DuckDuckGo/VPNAddWidgetTip.swift @@ -0,0 +1,78 @@ +// +// VPNAddWidgetTip.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 TipKit + +/// A tip to suggest to the user that they add our VPN widget for quick access to the VPN +/// +struct VPNAddWidgetTip {} + +/// Necessary split to support older iOS versions. +/// +@available(iOS 17.0, *) +extension VPNAddWidgetTip: Tip { + + enum ActionIdentifiers: String { + case addWidget = "com.duckduckgo.vpn.tip.addWidget.action.addWidget" + } + + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.geolocationTipDismissedEvent") + + static let snoozeTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.geolocationTipDismissedEvent") + + @Parameter(.transient) + static var vpnEnabled: Bool = false + + private static let vpnDisconnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.addWidget.vpnDisconnectedEvent") + + var id: String { + "com.duckduckgo.vpn.tip.addWidget" + } + + var title: Text { + Text("Add VPN Widget") + } + + var message: Text? { + Text("Turn the VPN on and off right from the Home Screen.") + } + + var image: Image? { + Image(.vpnAddWidgetTipIcon) + } + + var actions: [Action] { + [Action(id: ActionIdentifiers.addWidget.rawValue) { + Text("Add widget") + .foregroundStyle(Color(designSystemColor: .accent)) + }] + } + + var rules: [Rule] { + #Rule(Self.geolocationTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.snoozeTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.$vpnEnabled) { + $0 == false + } + } +} diff --git a/DuckDuckGo/VPNGeoswitchingTip.swift b/DuckDuckGo/VPNGeoswitchingTip.swift new file mode 100644 index 0000000000..e8110fa18c --- /dev/null +++ b/DuckDuckGo/VPNGeoswitchingTip.swift @@ -0,0 +1,58 @@ +// +// VPNGeoswitchingTip.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 TipKit + +/// A tip to suggest to the user to change their location using geo-switching +/// +struct VPNGeoswitchingTip {} + +@available(iOS 17.0, *) +extension VPNGeoswitchingTip: Tip { + + private static let vpnConnectedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.geoswitching.vpnConnectedEvent") + + var id: String { + "com.duckduckgo.vpn.tip.geoswitching" + } + + var title: Text { + Text("Change Your Location") + } + + var message: Text? { + Text("You can customize your VPN location by connecting to any of our servers worldwide.") + } + + var image: Image? { + Image(.vpnChangeLocationTipIcon) + } + + var rules: [Rule] { + #Rule(Self.vpnConnectedEvent) { + $0.donations.donatedWithin(.week).count > 0 + } + } + + static func donateVPNConnectedEvent() { + Task { + await vpnConnectedEvent.donate() + } + } +} diff --git a/DuckDuckGo/VPNSnoozeTip.swift b/DuckDuckGo/VPNSnoozeTip.swift new file mode 100644 index 0000000000..9baa7bacba --- /dev/null +++ b/DuckDuckGo/VPNSnoozeTip.swift @@ -0,0 +1,71 @@ +// +// VPNSnoozeTip.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 TipKit + +/// A tip to suggest to the user to use the snooze feature to momentarily disable the VPN +/// +struct VPNSnoozeTip {} + +/// Necessary split to support older iOS versions. +/// +@available(iOS 17.0, *) +extension VPNSnoozeTip: Tip { + + enum ActionIdentifiers: String { + case learnMore = "com.duckduckgo.vpn.tip.snooze.learnMoreId" + } + + static let geolocationTipDismissedEvent = Tips.Event(id: "com.duckduckgo.vpn.tip.snooze.geolocationTipDismissedEvent") + + @Parameter(.transient) + static var vpnEnabled: Bool = false + + var id: String { + "com.duckduckgo.vpn.tip.snooze" + } + + var title: Text { + Text("Avoid VPN Conflicts") + } + + var message: Text? { + Text("You can use sites or apps that block VPN traffic by snoozing the VPN connection.") + } + + var image: Image? { + Image(.vpnUseSnoozeTipIcon) + } + + var actions: [Action] { + [Action(id: ActionIdentifiers.learnMore.rawValue) { + Text("Learn more") + .foregroundStyle(Color(designSystemColor: .accent)) + }] + } + + var rules: [Rule] { + #Rule(Self.geolocationTipDismissedEvent) { + $0.donations.count > 0 + } + #Rule(Self.$vpnEnabled) { + $0 == true + } + } +} diff --git a/DuckDuckGo/ViewExtension.swift b/DuckDuckGo/ViewExtension.swift index 3c00ddf314..e322db6120 100644 --- a/DuckDuckGo/ViewExtension.swift +++ b/DuckDuckGo/ViewExtension.swift @@ -62,6 +62,12 @@ extension View { .applyBackground() } + /// Removes the grouped list style insets for a single row. + /// + func removeGroupedListStyleInsets() -> some View { + listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } + @ViewBuilder func applyBackground() -> some View { hideScrollContentBackground() diff --git a/DuckDuckGo/bg.lproj/Home.strings b/DuckDuckGo/bg.lproj/Home.strings deleted file mode 100644 index 7fdae5e57e..0000000000 --- a/DuckDuckGo/bg.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Търсене или въвеждане на адрес"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Търсене или въвеждане на адрес"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Настройки"; - diff --git a/DuckDuckGo/cs.lproj/Home.strings b/DuckDuckGo/cs.lproj/Home.strings deleted file mode 100644 index 0d1140d6fb..0000000000 --- a/DuckDuckGo/cs.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Vyhledejte nebo zadejte adresu"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Vyhledejte nebo zadejte adresu"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Nastavení"; - diff --git a/DuckDuckGo/da.lproj/Home.strings b/DuckDuckGo/da.lproj/Home.strings deleted file mode 100644 index 49cd94a66c..0000000000 --- a/DuckDuckGo/da.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Søg eller indtast adresse"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Søg eller indtast adresse"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Indstillinger"; - diff --git a/DuckDuckGo/de.lproj/Home.strings b/DuckDuckGo/de.lproj/Home.strings deleted file mode 100644 index 2b85d11b72..0000000000 --- a/DuckDuckGo/de.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Adresse suchen oder eingeben"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Adresse suchen oder eingeben"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Einstellungen"; - diff --git a/DuckDuckGo/el.lproj/Home.strings b/DuckDuckGo/el.lproj/Home.strings deleted file mode 100644 index c1355d51aa..0000000000 --- a/DuckDuckGo/el.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Αναζήτηση ή εισαγωγή διεύθυνσης"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Αναζήτηση ή εισαγωγή διεύθυνσης"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Ρυθμίσεις"; - diff --git a/DuckDuckGo/en.lproj/Localizable.strings b/DuckDuckGo/en.lproj/Localizable.strings index 04e1c6f25f..b4b69e4eb4 100644 --- a/DuckDuckGo/en.lproj/Localizable.strings +++ b/DuckDuckGo/en.lproj/Localizable.strings @@ -761,6 +761,21 @@ /* Button to answer question 'Did turning off protections resolve the issue on this site?' */ "broken.site.report.toggle.alert.yes.button" = "Yes"; +/* Button on the last screen of the onboarding, it will dismiss the onboarding screen. */ +"contextual.onboarding.addToDock.buttons.dismiss" = "Start Browsing"; + +/* Button at the end of the browser onboarding. On click it shows the user a video about how to add the application to the device dock. */ +"contextual.onboarding.addToDock.buttons.tutorial" = "Show Me How"; + +/* Message of the last screen of the onboarding to the browser app. */ +"contextual.onboarding.addToDock.endOfJourney.message" = "Remember, every time you browse with me a creepy ad loses its wings.\n\nSo keep me in your Dock for daily browsing."; + +/* The message of the onboarding dialog popup that explains how to add the DDG browser icon to the dock. */ +"contextual.onboarding.addToDock.tutorial.message" = "Find or search for the DuckDuckGo icon on your home screen. Then press and drag into place. That’s it!"; + +/* The title of the onboarding dialog popup that explains how to add the DDG browser icon to the dock. */ +"contextual.onboarding.addToDock.tutorial.title" = "Adding me to your Dock is easy."; + /* First parameter is a count of additional trackers, second and third are names of the tracker networks (strings) */ "contextual.onboarding.browsing.multiple.trackers" = "contextual.onboarding.browsing.multiple.trackers"; @@ -1053,7 +1068,7 @@ "duckplayer.settings.open-new-tab-label" = "Open Duck Player in a new tab"; /* Settings screen cell text for DuckPlayer settings */ -"duckplayer.settings.open-videos-in" = "Open Videos in Duck Player"; +"duckplayer.settings.open-videos-in" = "Open YouTube Videos in Duck Player"; /* Settings screen cell text for DuckPlayer settings */ "duckplayer.settings.title" = "Duck Player"; diff --git a/DuckDuckGo/es.lproj/Home.strings b/DuckDuckGo/es.lproj/Home.strings deleted file mode 100644 index 86ccb9ad50..0000000000 --- a/DuckDuckGo/es.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Buscar o introducir dirección"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Buscar o introducir dirección"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Ajustes"; - diff --git a/DuckDuckGo/et.lproj/Home.strings b/DuckDuckGo/et.lproj/Home.strings deleted file mode 100644 index f069c33b7f..0000000000 --- a/DuckDuckGo/et.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Otsi või sisesta aadress"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Otsi või sisesta aadress"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Seaded"; - diff --git a/DuckDuckGo/fi.lproj/Home.strings b/DuckDuckGo/fi.lproj/Home.strings deleted file mode 100644 index 4d5d9007a0..0000000000 --- a/DuckDuckGo/fi.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Hae tai anna osoite"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Hae tai anna osoite"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Asetukset"; - diff --git a/DuckDuckGo/fr.lproj/Home.strings b/DuckDuckGo/fr.lproj/Home.strings deleted file mode 100644 index 67a8943f55..0000000000 --- a/DuckDuckGo/fr.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Rechercher ou saisir une adresse"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Rechercher ou saisir une adresse"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Paramètres"; - diff --git a/DuckDuckGo/hr.lproj/Home.strings b/DuckDuckGo/hr.lproj/Home.strings deleted file mode 100644 index 2b4126c5d4..0000000000 --- a/DuckDuckGo/hr.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Pretraži ili unesi adresu"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Pretraži ili unesi adresu"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Postavke"; - diff --git a/DuckDuckGo/hu.lproj/Home.strings b/DuckDuckGo/hu.lproj/Home.strings deleted file mode 100644 index 5c5abd1350..0000000000 --- a/DuckDuckGo/hu.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Keresd meg, vagy írd be a címet."; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Keresd meg, vagy írd be a címet."; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Beállítások"; - diff --git a/DuckDuckGo/it.lproj/Home.strings b/DuckDuckGo/it.lproj/Home.strings deleted file mode 100644 index e2e5bf2c9a..0000000000 --- a/DuckDuckGo/it.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Cerca o digita l'indirizzo"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Cerca o digita l'indirizzo"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Impostazioni"; - diff --git a/DuckDuckGo/lt.lproj/Home.strings b/DuckDuckGo/lt.lproj/Home.strings deleted file mode 100644 index 95fec3ff09..0000000000 --- a/DuckDuckGo/lt.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Ieškoti arba įvesti adresą"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Ieškoti arba įvesti adresą"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Nustatymai"; - diff --git a/DuckDuckGo/lv.lproj/Home.strings b/DuckDuckGo/lv.lproj/Home.strings deleted file mode 100644 index 4d15e0fd53..0000000000 --- a/DuckDuckGo/lv.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Meklē vai ievadi adresi"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Meklē vai ievadi adresi"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Iestatījumi"; - diff --git a/DuckDuckGo/nb.lproj/Home.strings b/DuckDuckGo/nb.lproj/Home.strings deleted file mode 100644 index f9221ef8d2..0000000000 --- a/DuckDuckGo/nb.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Søk eller skriv inn adresse"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Søk eller skriv inn adresse"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Innstillinger"; - diff --git a/DuckDuckGo/nl.lproj/Home.strings b/DuckDuckGo/nl.lproj/Home.strings deleted file mode 100644 index 1c699305e2..0000000000 --- a/DuckDuckGo/nl.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Zoek of voer een adres in"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Zoek of voer een adres in"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Instellingen"; - diff --git a/DuckDuckGo/pl.lproj/Home.strings b/DuckDuckGo/pl.lproj/Home.strings deleted file mode 100644 index c7817ed475..0000000000 --- a/DuckDuckGo/pl.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Wyszukaj lub wprowadź adres"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Wyszukaj lub wprowadź adres"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Ustawienia"; - diff --git a/DuckDuckGo/pt.lproj/Home.strings b/DuckDuckGo/pt.lproj/Home.strings deleted file mode 100644 index 7438261d84..0000000000 --- a/DuckDuckGo/pt.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Pesquisar ou inserir endereço"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Pesquisar ou inserir endereço"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Definições"; - diff --git a/DuckDuckGo/ro.lproj/Home.strings b/DuckDuckGo/ro.lproj/Home.strings deleted file mode 100644 index 5f67865734..0000000000 --- a/DuckDuckGo/ro.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Caută sau introdu adresa"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Caută sau introdu adresa"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Setări"; - diff --git a/DuckDuckGo/ru.lproj/Home.strings b/DuckDuckGo/ru.lproj/Home.strings deleted file mode 100644 index 59032ca878..0000000000 --- a/DuckDuckGo/ru.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Введите поисковый запрос или адрес сайта"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Введите поисковый запрос или адрес сайта"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Настройки"; - diff --git a/DuckDuckGo/sk.lproj/Home.strings b/DuckDuckGo/sk.lproj/Home.strings deleted file mode 100644 index eae348e961..0000000000 --- a/DuckDuckGo/sk.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Vyhľadajte alebo zadajte adresu"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Vyhľadajte alebo zadajte adresu"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Nastavenia"; - diff --git a/DuckDuckGo/sl.lproj/Home.strings b/DuckDuckGo/sl.lproj/Home.strings deleted file mode 100644 index 81a1f46940..0000000000 --- a/DuckDuckGo/sl.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Poišči ali vnesi naslov"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Poišči ali vnesi naslov"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Nastavitve"; - diff --git a/DuckDuckGo/sv.lproj/Home.strings b/DuckDuckGo/sv.lproj/Home.strings deleted file mode 100644 index 103d317ee2..0000000000 --- a/DuckDuckGo/sv.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Sök eller ange adress"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Sök eller ange adress"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Inställningar"; - diff --git a/DuckDuckGo/tr.lproj/Home.strings b/DuckDuckGo/tr.lproj/Home.strings deleted file mode 100644 index a8df44e0ee..0000000000 --- a/DuckDuckGo/tr.lproj/Home.strings +++ /dev/null @@ -1,9 +0,0 @@ -/* Class = "UIView"; accessibilityLabel = "Search or enter address"; ObjectID = "7Jf-KE-oXj"; */ -"7Jf-KE-oXj.accessibilityLabel" = "Adres ara veya gir"; - -/* Class = "UILabel"; text = "Search or enter address"; ObjectID = "73D-OT-n1K"; */ -"73D-OT-n1K.text" = "Adres ara veya gir"; - -/* Class = "UIButton"; accessibilityLabel = "Settings"; ObjectID = "kcN-YB-mZi"; */ -"kcN-YB-mZi.accessibilityLabel" = "Ayarlar"; - diff --git a/DuckDuckGoTests/AddToDockPromoViewModelTests.swift b/DuckDuckGoTests/AddToDockPromoViewModelTests.swift new file mode 100644 index 0000000000..d8e7b22ee9 --- /dev/null +++ b/DuckDuckGoTests/AddToDockPromoViewModelTests.swift @@ -0,0 +1,97 @@ +// +// AddToDockPromoViewModelTests.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 Lottie +@testable import DuckDuckGo + +final class AddToDockPromoViewModelTests: XCTestCase { + private var sut: AddToDockPromoViewModel! + private var managerMock: AppIconManagerMock! + + override func setUpWithError() throws { + try super.setUpWithError() + + managerMock = .init() + sut = .init(appIconManager: managerMock) + } + + override func tearDownWithError() throws { + managerMock = nil + sut = nil + try super.tearDownWithError() + } + + func testWhenColorIsCalledThenReturnExpectedColor() { + // GIVEN + managerMock.appIcon = .red + + // WHEN + var result = sut.color + + // THEN + XCTAssertEqual(result, LottieColor(r: 0.87, g: 0.34, b: 0.2, a: 1.0)) + + // GIVEN + managerMock.appIcon = .yellow + + // WHEN + result = sut.color + + // THEN + XCTAssertEqual(result, LottieColor(r: 0.89, g: 0.64, b: 0.07, a: 1.0)) + + // GIVEN + managerMock.appIcon = .green + + // WHEN + result = sut.color + + // THEN + XCTAssertEqual(result, LottieColor(r: 0.22, g: 0.62, b: 0.16, a: 1.0)) + + // GIVEN + managerMock.appIcon = .blue + + // WHEN + result = sut.color + + // THEN + XCTAssertEqual(result, LottieColor(r: 0.22, g: 0.41, b: 0.94, a: 1.0)) + + // GIVEN + managerMock.appIcon = .purple + + // WHEN + result = sut.color + + // THEN + XCTAssertEqual(result, LottieColor(r: 0.42, g: 0.31, b: 0.73, a: 1.0)) + + // GIVEN + managerMock.appIcon = .black + + // WHEN + result = sut.color + + // THEN + XCTAssertEqual(result, LottieColor(r: 0, g: 0, b: 0, a: 1.0)) + } + +} diff --git a/DuckDuckGoTests/AppSettingsMock.swift b/DuckDuckGoTests/AppSettingsMock.swift index dab92872d3..a8c3db67bf 100644 --- a/DuckDuckGoTests/AppSettingsMock.swift +++ b/DuckDuckGoTests/AppSettingsMock.swift @@ -95,6 +95,6 @@ class AppSettingsMock: AppSettings { var newTabPageIntroMessageSeenCount: Int = 0 var onboardingHighlightsEnabled: Bool = false - + var onboardingAddToDockEnabled: Bool = false } diff --git a/DuckDuckGoTests/BookmarksExporterTests.swift b/DuckDuckGoTests/BookmarksExporterTests.swift index 12d3fe02c2..a0af75f3a1 100644 --- a/DuckDuckGoTests/BookmarksExporterTests.swift +++ b/DuckDuckGoTests/BookmarksExporterTests.swift @@ -247,7 +247,7 @@ class BookmarksExporterTests: XCTestCase { url: "https://www.wsj.com/?mod=wsjheader_logo"), BookmarksExporter.Template.closeFolder(level: level), BookmarksExporter.Template.bookmark(level: level, - title: "DuckDuckGo — Privacy, simplified.", + title: "DuckDuckGo — Your protection, our priority.", url: "https://duckduckgo.com/"), BookmarksExporter.Template.openFolder(level: level, named: "DupeFolderNameContents"), BookmarksExporter.Template.bookmark(level: level + 1, diff --git a/DuckDuckGoTests/ContextualDaxDialogsFactoryTests.swift b/DuckDuckGoTests/ContextualDaxDialogsFactoryTests.swift index cf4b1ba610..7f7665d7a6 100644 --- a/DuckDuckGoTests/ContextualDaxDialogsFactoryTests.swift +++ b/DuckDuckGoTests/ContextualDaxDialogsFactoryTests.swift @@ -202,7 +202,7 @@ final class ContextualDaxDialogsFactoryTests: XCTestCase { XCTAssertFalse(delegate.didCallDidTapDismissContextualOnboardingAction) // WHEN - view.highFiveAction() + view.dismissAction(false) // THEN XCTAssertTrue(delegate.didCallDidTapDismissContextualOnboardingAction) @@ -330,7 +330,7 @@ final class ContextualDaxDialogsFactoryTests: XCTestCase { XCTAssertFalse(pixelReporterMock.didCallTrackEndOfJourneyDialogDismiss) // WHEN - view.highFiveAction() + view.dismissAction(false) // THEN XCTAssertTrue(pixelReporterMock.didCallTrackEndOfJourneyDialogDismiss) diff --git a/DuckDuckGoTests/ContextualOnboardingNewTabDialogFactoryTests.swift b/DuckDuckGoTests/ContextualOnboardingNewTabDialogFactoryTests.swift index 152b5523e4..8d54d53d5a 100644 --- a/DuckDuckGoTests/ContextualOnboardingNewTabDialogFactoryTests.swift +++ b/DuckDuckGoTests/ContextualOnboardingNewTabDialogFactoryTests.swift @@ -101,7 +101,7 @@ class ContextualOnboardingNewTabDialogFactoryTests: XCTestCase { // Then let finalDialog = find(OnboardingFinalDialog.self, in: host) XCTAssertNotNil(finalDialog) - finalDialog?.highFiveAction() + finalDialog?.dismissAction(false) XCTAssertTrue(onDismissedRun) wait(for: [expectation], timeout: 5.0) XCTAssertTrue(contextualOnboardingLogicMock.didCallsetFinalOnboardingDialogSeen) @@ -157,7 +157,7 @@ class ContextualOnboardingNewTabDialogFactoryTests: XCTestCase { XCTAssertFalse(pixelReporterMock.didCallTrackEndOfJourneyDialogDismiss) // WHEN - finalDialog.highFiveAction() + finalDialog.dismissAction(false) // THEN XCTAssertTrue(pixelReporterMock.didCallTrackEndOfJourneyDialogDismiss) diff --git a/DuckDuckGoTests/DownloadManagerTests.swift b/DuckDuckGoTests/DownloadManagerTests.swift index 86367d7144..ec613dee5a 100644 --- a/DuckDuckGoTests/DownloadManagerTests.swift +++ b/DuckDuckGoTests/DownloadManagerTests.swift @@ -26,8 +26,6 @@ import WidgetKit class DownloadManagerTests: XCTestCase { private let downloadManagerTestsHelper = DownloadTestsHelper(downloadsDirectory: DownloadManager().downloadsDirectory) - var mockDependencyProvider: MockDependencyProvider! - override func tearDown() { super.tearDown() downloadManagerTestsHelper.deleteAllFiles() diff --git a/DuckDuckGoTests/DuckPlayerExperimentTests.swift b/DuckDuckGoTests/DuckPlayerExperimentTests.swift deleted file mode 100644 index 56040b0dee..0000000000 --- a/DuckDuckGoTests/DuckPlayerExperimentTests.swift +++ /dev/null @@ -1,435 +0,0 @@ -// -// DuckPlayerExperimentTests.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 -import Core - -public class MockDuckPlayerExperimentDateProvider: DuckPlayerExperimentDateProvider { - private var customDate: Date? - - public var currentDate: Date { - return customDate ?? Date() - } - - public init(customDate: Date? = nil) { - self.customDate = customDate - } - - public func setCurrentDate(_ date: Date) { - self.customDate = date - } - - public func resetToCurrentDate() { - self.customDate = nil - } -} - - -final class DuckPlayerExperimentPixelFireMock: DuckPlayerExperimentPixelFiring { - - static private(set) var capturedPixelEventHistory: [(pixel: Pixel.Event, params: [String: String])] = [] - - static func fireDuckPlayerExperimentPixel(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) { - capturedPixelEventHistory.append((pixel: pixel, params: params)) - } - - static func tearDown() { - capturedPixelEventHistory = [] - } -} - - -final class DuckPlayerExperimentDailyPixelFireMock: DuckPlayerExperimentPixelFiring { - - static private(set) var capturedPixelEventHistory: [(pixel: Pixel.Event, params: [String: String])] = [] - - static func fireDuckPlayerExperimentPixel(pixel: Pixel.Event, withAdditionalParameters params: [String: String]) { - capturedPixelEventHistory.append((pixel: pixel, params: params)) - } - - static func tearDown() { - capturedPixelEventHistory = [] - } -} - - -final class DuckPlayerLaunchExperimentTests: XCTestCase { - - private var sut: DuckPlayerLaunchExperiment! - private var userDefaults: UserDefaults! - private var dateProvider = MockDuckPlayerExperimentDateProvider() - - override func setUp() { - super.setUp() - // Setting up a temporary UserDefaults to isolate tests - userDefaults = UserDefaults(suiteName: "DuckPlayerLaunchExperimentTests") - userDefaults.removePersistentDomain(forName: "DuckPlayerLaunchExperimentTests") - } - - override func tearDown() { - sut = nil - userDefaults = nil - DuckPlayerExperimentPixelFireMock.tearDown() - DuckPlayerExperimentDailyPixelFireMock.tearDown() - super.tearDown() - } - - func testAssignUserToCohort_AssignsCohotsAndFiresPixels() { - - // Set a fixed date to 2024.09.10 - dateProvider.setCurrentDate(Date(timeIntervalSince1970: 1725926400)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - - sut.cleanup() - XCTAssertFalse(sut.isEnrolled, "User should not be enrolled initially.") - - sut.assignUserToCohort() - - XCTAssertTrue(sut.isEnrolled, "User should be enrolled after assigning to cohort.") - XCTAssertNotNil(sut.experimentCohortV2, "Experiment cohort should be assigned.") - XCTAssertNotNil(sut.enrollmentDateV2, "Enrollment date should be set.") - XCTAssertEqual(DuckPlayerLaunchExperiment.formattedDate(sut.enrollmentDateV2 ?? Date()), "20240910", "The assigned date should match.") - - // Check the pixel event history - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - XCTAssertEqual(history.count, 1, "One pixel event should be fired.") - if let firstEvent = history.first { - XCTAssertEqual(firstEvent.pixel, .duckplayerExperimentCohortAssign, "Enrollment pixel should be duckplayerExperimentCohortAssign") - XCTAssert(["control", "experiment"].contains(firstEvent.params["variant"]), "The variant is incorrect") - XCTAssertEqual(firstEvent.params["enrollment"], "20240910", "The assigned date should be valid.") - } - } - - func testAssignUserToCohortMultipleTimes_DoesNotReassignNorFiresMultiplePixels() { - - // Set a fixed date to 2024.09.10 - dateProvider.setCurrentDate(Date(timeIntervalSince1970: 1725926400)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - - sut.cleanup() - XCTAssertFalse(sut.isEnrolled, "User should not be enrolled initially.") - - sut.assignUserToCohort() - XCTAssertEqual(DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory.count, 1, "Enrollment pixel should have fired") - - DuckPlayerExperimentPixelFireMock.tearDown() - - // Change the date to something in the future - dateProvider.setCurrentDate(Date(timeIntervalSince1970: 1726185600)) - sut.assignUserToCohort() - XCTAssertEqual(DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory.count, 0, "Enrollment pixel should not have fired again") - XCTAssertEqual(sut.isEnrolled, true, "The assigned date should not change.") - XCTAssertEqual(DuckPlayerLaunchExperiment.formattedDate(sut.enrollmentDateV2 ?? Date()), "20240910", "The assigned date should not change.") - } - - func testIfUserIsEnrolled_SearchDailyPixelsFire() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - sut.assignUserToCohort() - - for day in 0...14 { - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (Double(day) * 86400))) - - // Fire a random number of search pixels per day, but only one should be registered - for _ in 1...Int.random(in: 1..<10) { - sut.fireSearchPixels() - } - } - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let dailyPixel = history.filter { $0.pixel == .duckplayerExperimentDailySearch } - - // Assign cohort - XCTAssertEqual(dailyPixel.count, 15, "There must be 15 daily pixels") - - for (index, value) in dailyPixel.enumerated() { - XCTAssertEqual(value.params["day"], "\(index)") - XCTAssert(["control", "experiment"].contains(value.params["variant"]), "The variant is incorrect") - XCTAssertEqual(value.params["enrollment"], "20240910", "The assigned date is incorrect.") - } - - } - - func testIfUserIsEnrolled_SearchDailyPixelsFireWhenNotUsedDaily() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - // Assign cohort - sut.assignUserToCohort() - - let fireDays = [0, 4, 11, 12] - for day in fireDays { - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (Double(day) * 86400))) - - // Fire a random number of search pixels per day, but only one should be registered - for _ in 1...Int.random(in: 1..<10) { - sut.fireSearchPixels() - } - } - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let dailyPixel = history.filter { $0.pixel == .duckplayerExperimentDailySearch } - - XCTAssertEqual(dailyPixel.count, 4, "There must be 4 daily pixels") - - if dailyPixel.count == 4 { - XCTAssertEqual(dailyPixel[0].params["day"], "0") - XCTAssertEqual(dailyPixel[1].params["day"], "4") - XCTAssertEqual(dailyPixel[2].params["day"], "11") - XCTAssertEqual(dailyPixel[3].params["day"], "12") - } - } - - func testIfUserIsEnrolled_WeeklyPixelsFire() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - // Assign cohort - sut.assignUserToCohort() - - for day in 0...13 { - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (Double(day) * 86400))) - - // Fire a random number of search pixels per day, but only one should be registered - for _ in 1...Int.random(in: 1..<10) { - sut.fireSearchPixels() - } - } - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let weeklyPixel = history.filter { $0.pixel == .duckplayerExperimentWeeklySearch } - - XCTAssertEqual(weeklyPixel.count, 2, "There must be 2 weekly pixels") - - for (index, value) in weeklyPixel.enumerated() { - XCTAssertEqual(value.params["week"], "\(index+1)") - XCTAssert(["control", "experiment"].contains(value.params["variant"]), "The variant is incorrect") - XCTAssertEqual(value.params["enrollment"], "20240910", "The assigned date is incorrect.") - - } - - } - - func testIfUserIsEnrolled_WeeklyPixelsFireWhenNotUsedDaily() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - // Assign cohort - sut.assignUserToCohort() - - let fireDays = [0, 6, 11, 12] - for day in fireDays { - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (Double(day) * 86400))) - - // Fire a random number of search pixels per day - for _ in 1...Int.random(in: 1..<10) { - sut.fireSearchPixels() - } - } - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let weeklyPixel = history.filter { $0.pixel == .duckplayerExperimentWeeklySearch } - - XCTAssertEqual(weeklyPixel.count, 2, "There must be 2 weekly pixels") - - if weeklyPixel.count == 2 { - XCTAssertEqual(weeklyPixel[0].params["week"], "1") - XCTAssertEqual(weeklyPixel[1].params["week"], "2") - } - - } - - func testIfUserIsEnrolled_WeeklyPixelsFireWhenNotUsedWeek2() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - sut.assignUserToCohort() - - let fireDays = [0, 2, 3, 6] - for day in fireDays { - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (Double(day) * 86400))) - - // Fire a random number of search pixels per day - for _ in 1...Int.random(in: 1..<10) { - sut.fireSearchPixels() - } - } - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let weeklyPixel = history.filter { $0.pixel == .duckplayerExperimentWeeklySearch } - - // Assign cohort - XCTAssertEqual(weeklyPixel.count, 1, "There must be 2 weekly pixels") - - if weeklyPixel.count == 2 { - XCTAssertEqual(weeklyPixel[0].params["week"], "1") - } - - } - - func testIfUserIsEnrolled_WeeklyPixelsFireWhenNotUsedWeek1() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - // Assign cohort - sut.assignUserToCohort() - - let fireDays = [0, 8, 9, 12] - for day in fireDays { - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (Double(day) * 86400))) - - // Fire a random number of search pixels per day - for _ in 1...Int.random(in: 1..<10) { - sut.fireSearchPixels() - } - } - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let weeklyPixel = history.filter { $0.pixel == .duckplayerExperimentWeeklySearch } - - XCTAssertEqual(weeklyPixel.count, 1, "There must be 2 weekly pixels") - - if weeklyPixel.count == 2 { - XCTAssertEqual(weeklyPixel[0].params["week"], "2") - } - - } - - func testIfUserIsEnrolled_SearchPixelFires() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(userDefaults: userDefaults, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - // Assign cohort - sut.assignUserToCohort() - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - sut.fireSearchPixels() - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - - let searchPixel = history.filter { $0.pixel == .duckplayerExperimentSearch } - - if history.count == 1 { - XCTAssert(["control", "experiment"].contains(searchPixel.first?.params["variant"]), "The variant is incorrect") - XCTAssertEqual(searchPixel.first?.params["enrollment"], "20240910", "The assigned date should be valid.") - } - - } - - func testIfUserIsEnrolled_YoutubePixelFires() { - - // Set a fixed date to 2024.09.10 - let startDate: Double = 1725926400 - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate)) - - let sut = DuckPlayerLaunchExperiment(duckPlayerMode: .alwaysAsk, - referrer: .serp, - pixel: DuckPlayerExperimentPixelFireMock.self, - dateProvider: dateProvider) - sut.cleanup() - - sut.assignUserToCohort() - - XCTAssertTrue(sut.isEnrolled, "User should be enrolled after assigning to cohort.") - - dateProvider.setCurrentDate(Date(timeIntervalSince1970: startDate + (3 * 86400))) // Day 3 - sut.fireYoutubePixel(videoID: "testVideoID") - - let history = DuckPlayerExperimentPixelFireMock.capturedPixelEventHistory - let youtubePixel = history.filter { $0.pixel == .duckplayerExperimentYoutubePageView } - - // Validate that one YouTube pixel was fired - XCTAssertEqual(youtubePixel.count, 1, "There should be exactly one YouTube pixel fired.") - - if let firedPixel = youtubePixel.first { - XCTAssertEqual(firedPixel.params["day"], "3", "The day parameter is incorrect.") - XCTAssert(["control", "experiment"].contains(firedPixel.params["variant"]), "The variant is incorrect.") - XCTAssertEqual(firedPixel.params["enrollment"], "20240910", "The enrollment date should be valid.") - XCTAssertEqual(firedPixel.params["state"], "alwaysAsk", "The state parameter is incorrect.") - XCTAssertEqual(firedPixel.params["referrer"], "serp", "The referrer parameter is incorrect.") - } - - } - -} diff --git a/DuckDuckGoTests/DuckPlayerMocks.swift b/DuckDuckGoTests/DuckPlayerMocks.swift index cf387e5578..f593fa4dd9 100644 --- a/DuckDuckGoTests/DuckPlayerMocks.swift +++ b/DuckDuckGoTests/DuckPlayerMocks.swift @@ -44,52 +44,119 @@ class MockWKNavigationDelegate: NSObject, WKNavigationDelegate { } class MockWebView: WKWebView { - var didStopLoadingCalled = false + + var lastLoadedRequest: URLRequest? - var lastResponseHTML: String? - var goToCalledWith: WKBackForwardListItem? - var canGoBackMock = false - var currentURL: URL? + var loadedRequests: [URLRequest] = [] + var loadCallCount = 0 + var loadCompletionHandler: (() -> Void)? + + /// The current URL of the web view. private var _url: URL? override var url: URL? { - return currentURL + return _url } - + + /// Sets the current URL of the web view. func setCurrentURL(_ url: URL) { - self.currentURL = url - } - - override func stopLoading() { - didStopLoadingCalled = true + self._url = url } - + + /// A simulated history stack to support navigation methods like `goBack()`. + var historyStack: [URL] = [] + + /// Indicates whether the `stopLoading()` method was called. + var didStopLoadingCalled = false + + // MARK: - Overridden Methods + override func load(_ request: URLRequest) -> WKNavigation? { lastLoadedRequest = request + loadedRequests.append(request) + loadCallCount += 1 + + // Simulate asynchronous loading + DispatchQueue.main.async { + self.loadCompletionHandler?() + } + return nil } - + override func reload() -> WKNavigation? { + // Simulate reload behavior if needed return nil } + + override func goBack() -> WKNavigation? { + if historyStack.count > 1 { + // Remove the current page + historyStack.removeLast() + // Set the URL to the previous page + setCurrentURL(historyStack.last!) + } + return nil + } + + override func stopLoading() { + didStopLoadingCalled = true + } + + // MARK: - Additional Helper Methods (if needed) + + /// Simulates navigating to a new URL. + func navigate(to url: URL) { + historyStack.append(url) + setCurrentURL(url) + } + + /// Resets the web view's state. + func reset() { + lastLoadedRequest = nil + loadedRequests.removeAll() + loadCallCount = 0 + didStopLoadingCalled = false + historyStack.removeAll() + _url = nil + loadCompletionHandler = nil + } } class MockNavigationAction: WKNavigationAction { private let _request: URLRequest private let _navigationType: WKNavigationType - - init(request: URLRequest, navigationType: WKNavigationType = .linkActivated ) { + private let _targetFrame: WKFrameInfo? + + init(request: URLRequest, navigationType: WKNavigationType = .linkActivated, targetFrame: WKFrameInfo? = nil) { self._request = request self._navigationType = navigationType + self._targetFrame = targetFrame } - + override var request: URLRequest { return _request } - + override var navigationType: WKNavigationType { return _navigationType } + + override var targetFrame: WKFrameInfo? { + return _targetFrame + } +} + +class MockFrameInfo: WKFrameInfo { + private let _isMainFrame: Bool + + init(isMainFrame: Bool) { + self._isMainFrame = isMainFrame + } + + override var isMainFrame: Bool { + return _isMainFrame + } } final class MockDuckPlayerSettings: DuckPlayerSettings { @@ -102,6 +169,7 @@ final class MockDuckPlayerSettings: DuckPlayerSettings { var mode: DuckPlayerMode = .disabled var askModeOverlayHidden: Bool = false var allowFirstVideo: Bool = false + var openInNewTab: Bool = false init(appSettings: AppSettings = AppSettingsMock(), privacyConfigManager: any BrowserServicesKit.PrivacyConfigurationManaging) {} func triggerNotification() {} @@ -116,7 +184,12 @@ final class MockDuckPlayerSettings: DuckPlayerSettings { } -final class MockDuckPlayer: DuckPlayerProtocol { +final class MockDuckPlayer: DuckPlayerControlling { + + func telemetryEvent(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + var hostView: UIViewController? @@ -129,6 +202,7 @@ final class MockDuckPlayer: DuckPlayerProtocol { } func setHostViewController(_ vc: UIViewController) {} + func removeHostView() {} func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> (any Encodable)? { nil @@ -163,13 +237,35 @@ final class MockDuckPlayer: DuckPlayerProtocol { } } +enum MockFeatureFlag: Hashable { + case duckPlayer, duckPlayerOpenInNewTab +} + final class MockDuckPlayerFeatureFlagger: FeatureFlagger { - func isFeatureOn(forProvider: F) -> Bool where F: BrowserServicesKit.FeatureFlagSourceProviding { - return true + var enabledFeatures: Set = [] + + func isFeatureOn(_ feature: MockFeatureFlag) -> Bool { + return enabledFeatures.contains(feature) + } + + func isFeatureOn(forProvider provider: F) -> Bool where F: FeatureFlagSourceProviding { + return !enabledFeatures.isEmpty } - } final class MockDuckPlayerStorage: DuckPlayerStorage { var userInteractedWithDuckPlayer: Bool = false } + +final class MockDuckPlayerTabNavigator: DuckPlayerTabNavigationHandling { + var openedURL: URL? + var closeTabCalled = false + + func openTab(for url: URL) { + openedURL = url + } + + func closeTab() { + closeTabCalled = true + } +} diff --git a/DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift b/DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift new file mode 100644 index 0000000000..dcdebd766c --- /dev/null +++ b/DuckDuckGoTests/FavoritesListInteractingAdapterTests.swift @@ -0,0 +1,91 @@ +// +// FavoritesListInteractingAdapterTests.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 Combine +import Bookmarks + +@testable import DuckDuckGo + +final class FavoritesListInteractingAdapterTests: XCTestCase { + + private var favoritesListInteracting: MockFavoritesListInteracting! + private var appSettings: AppSettingsMock! + + private var cancellables: Set = [] + + override func setUpWithError() throws { + favoritesListInteracting = MockFavoritesListInteracting() + appSettings = AppSettingsMock() + } + + override func tearDownWithError() throws { + cancellables.removeAll() + } + + func testPublishesUpdateWhenFavoritesDisplayModeChanges() { + let expectation = XCTestExpectation(description: #function) + let sut = createSUT() + + sut.externalUpdates.sink { + XCTAssertTrue(Thread.isMainThread) + expectation.fulfill() + } + .store(in: &cancellables) + + NotificationCenter.default.post(name: AppUserDefaults.Notifications.favoritesDisplayModeChange, object: nil) + + wait(for: [expectation], timeout: 0.1) + } + + func testPublishesUpdateOnExternalListUpdate() { + let expectation = XCTestExpectation(description: #function) + let publisher = PassthroughSubject() + favoritesListInteracting.externalUpdates = publisher.eraseToAnyPublisher() + + let sut = createSUT() + + sut.externalUpdates.sink { + XCTAssertTrue(Thread.isMainThread) + expectation.fulfill() + } + .store(in: &cancellables) + + publisher.send() + + wait(for: [expectation], timeout: 0.1) + } + + private func createSUT() -> FavoritesListInteractingAdapter { + return FavoritesListInteractingAdapter(favoritesListInteracting: favoritesListInteracting, appSettings: appSettings) + } +} + +private class FavoritesListInteractingMock: FavoritesListInteracting { + var favoritesDisplayMode: Bookmarks.FavoritesDisplayMode = .displayNative(.mobile) + var favorites: [Bookmarks.BookmarkEntity] = [] + func favorite(at index: Int) -> Bookmarks.BookmarkEntity? { + return nil + } + func removeFavorite(_ favorite: Bookmarks.BookmarkEntity) {} + func moveFavorite(_ favorite: Bookmarks.BookmarkEntity, fromIndex: Int, toIndex: Int) { } + var externalUpdates: AnyPublisher = PassthroughSubject().eraseToAnyPublisher() + var localUpdates: AnyPublisher = PassthroughSubject().eraseToAnyPublisher() + func reloadData() {} +} diff --git a/DuckDuckGoTests/MockDependencyProvider.swift b/DuckDuckGoTests/MockDependencyProvider.swift deleted file mode 100644 index 05e28803f7..0000000000 --- a/DuckDuckGoTests/MockDependencyProvider.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// MockDependencyProvider.swift -// DuckDuckGo -// -// Copyright © 2018 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 BrowserServicesKit -import DDGSync -import Subscription -import SubscriptionTestingUtilities -import NetworkProtection -import RemoteMessaging -@testable import DuckDuckGo - -class MockDependencyProvider: DependencyProvider { - var appSettings: AppSettings - var variantManager: VariantManager - var featureFlagger: FeatureFlagger - var internalUserDecider: InternalUserDecider - var storageCache: StorageCache - var downloadManager: DownloadManager - var autofillLoginSession: AutofillLoginSession - var autofillNeverPromptWebsitesManager: AutofillNeverPromptWebsitesManager - var configurationManager: ConfigurationManager - var configurationStore: ConfigurationStore - var userBehaviorMonitor: UserBehaviorMonitor - var subscriptionManager: SubscriptionManager - var accountManager: AccountManager - var vpnFeatureVisibility: DefaultNetworkProtectionVisibility - var networkProtectionKeychainTokenStore: NetworkProtectionKeychainTokenStore - var networkProtectionTunnelController: NetworkProtectionTunnelController - var connectionObserver: NetworkProtection.ConnectionStatusObserver - var serverInfoObserver: NetworkProtection.ConnectionServerInfoObserver - var vpnSettings: NetworkProtection.VPNSettings - var persistentPixel: PersistentPixelFiring - - init() { - let defaultProvider = AppDependencyProvider.makeTestingInstance() - appSettings = defaultProvider.appSettings - variantManager = defaultProvider.variantManager - featureFlagger = defaultProvider.featureFlagger - internalUserDecider = defaultProvider.internalUserDecider - storageCache = defaultProvider.storageCache - downloadManager = defaultProvider.downloadManager - autofillLoginSession = defaultProvider.autofillLoginSession - autofillNeverPromptWebsitesManager = defaultProvider.autofillNeverPromptWebsitesManager - configurationStore = defaultProvider.configurationStore - configurationManager = defaultProvider.configurationManager - userBehaviorMonitor = defaultProvider.userBehaviorMonitor - - accountManager = AccountManagerMock() - - let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: .production) - let authService = DefaultAuthEndpointService(currentServiceEnvironment: .production) - let storePurchaseManager = DefaultStorePurchaseManager() - subscriptionManager = SubscriptionManagerMock(accountManager: accountManager, - subscriptionEndpointService: subscriptionService, - authEndpointService: authService, - storePurchaseManager: storePurchaseManager, - currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, - purchasePlatform: .appStore), - canPurchase: true) - - let accessTokenProvider: () -> String? = { { "sometoken" } }() - networkProtectionKeychainTokenStore = NetworkProtectionKeychainTokenStore(accessTokenProvider: accessTokenProvider) - networkProtectionTunnelController = NetworkProtectionTunnelController(accountManager: accountManager, - tokenStore: networkProtectionKeychainTokenStore, - persistentPixel: MockPersistentPixel()) - vpnFeatureVisibility = DefaultNetworkProtectionVisibility(userDefaults: .networkProtectionGroupDefaults, - accountManager: accountManager) - - connectionObserver = ConnectionStatusObserverThroughSession() - serverInfoObserver = ConnectionServerInfoObserverThroughSession() - vpnSettings = VPNSettings(defaults: .networkProtectionGroupDefaults) - persistentPixel = MockPersistentPixel() - } -} diff --git a/DuckDuckGoTests/MockFiles/bookmarks_brave.html b/DuckDuckGoTests/MockFiles/bookmarks_brave.html index 7ba22d7cdd..d6c224a11b 100644 --- a/DuckDuckGoTests/MockFiles/bookmarks_brave.html +++ b/DuckDuckGoTests/MockFiles/bookmarks_brave.html @@ -47,7 +47,7 @@

Bookmarks

The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video

-

DuckDuckGo — Privacy, simplified. +
DuckDuckGo — Your protection, our priority.

DupeFolderNameContents

MacRumors: Apple News and Rumors diff --git a/DuckDuckGoTests/MockFiles/bookmarks_chrome.html b/DuckDuckGoTests/MockFiles/bookmarks_chrome.html index 08fca4e9b7..54f9bdb6ae 100644 --- a/DuckDuckGoTests/MockFiles/bookmarks_chrome.html +++ b/DuckDuckGoTests/MockFiles/bookmarks_chrome.html @@ -48,7 +48,7 @@

Bookmarks

The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video

-

DuckDuckGo — Privacy, simplified. +
DuckDuckGo — Your protection, our priority.

DupeFolderNameContents

MacRumors: Apple News and Rumors diff --git a/DuckDuckGoTests/MockFiles/bookmarks_ddg_android.html b/DuckDuckGoTests/MockFiles/bookmarks_ddg_android.html index 8a87fa9f41..1e7cb6bf92 100644 --- a/DuckDuckGoTests/MockFiles/bookmarks_ddg_android.html +++ b/DuckDuckGoTests/MockFiles/bookmarks_ddg_android.html @@ -47,7 +47,7 @@

Bookmarks

The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video

-

DuckDuckGo — Privacy, simplified. +
DuckDuckGo — Your protection, our priority.

DupeFolderNameContents

MacRumors: Apple News and Rumors diff --git a/DuckDuckGoTests/MockFiles/bookmarks_ddg_macos.html b/DuckDuckGoTests/MockFiles/bookmarks_ddg_macos.html index 9965f7d234..d3027ccc37 100644 --- a/DuckDuckGoTests/MockFiles/bookmarks_ddg_macos.html +++ b/DuckDuckGoTests/MockFiles/bookmarks_ddg_macos.html @@ -42,7 +42,7 @@

Bookmarks

The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video

-

DuckDuckGo — Privacy, simplified. +
DuckDuckGo — Your protection, our priority.

DupeFolderNameContents

MacRumors: Apple News and Rumors diff --git a/DuckDuckGoTests/MockFiles/bookmarks_firefox.html b/DuckDuckGoTests/MockFiles/bookmarks_firefox.html index 92c60fcd13..24f2804799 100644 --- a/DuckDuckGoTests/MockFiles/bookmarks_firefox.html +++ b/DuckDuckGoTests/MockFiles/bookmarks_firefox.html @@ -55,7 +55,7 @@

Bookmarks Menu

The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video

-

DuckDuckGo — Privacy, simplified. +
DuckDuckGo — Your protection, our priority.

DupeFolderNameContents

MacRumors: Apple News and Rumors diff --git a/DuckDuckGoTests/MockFiles/bookmarks_safari.html b/DuckDuckGoTests/MockFiles/bookmarks_safari.html index 560cbf6533..cf1e87b8be 100644 --- a/DuckDuckGoTests/MockFiles/bookmarks_safari.html +++ b/DuckDuckGoTests/MockFiles/bookmarks_safari.html @@ -53,7 +53,7 @@

Bookmarks

The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video

-

DuckDuckGo — Privacy, simplified. +
DuckDuckGo — Your protection, our priority.

DupeFolderNameContents

MacRumors: Apple News and Rumors diff --git a/DuckDuckGoTests/MockTabDelegate.swift b/DuckDuckGoTests/MockTabDelegate.swift index 3cd9a7c74c..b37a63a332 100644 --- a/DuckDuckGoTests/MockTabDelegate.swift +++ b/DuckDuckGoTests/MockTabDelegate.swift @@ -24,6 +24,8 @@ import BrowserServicesKit import PrivacyDashboard import Core import Persistence +import Subscription +import SubscriptionTestingUtilities @testable import DuckDuckGo final class MockTabDelegate: TabDelegate { @@ -134,7 +136,8 @@ extension TabViewController { contextualOnboardingLogic: contextualOnboardingLogic, onboardingPixelReporter: contextualOnboardingPixelReporter, urlCredentialCreator: MockCredentialCreator(), - featureFlagger: featureFlagger + featureFlagger: featureFlagger, + subscriptionCookieManager: SubscriptionCookieManagerMock() ) tab.attachWebView(configuration: .nonPersistent(), andLoadRequest: nil, consumeCookies: false, customWebView: customWebView) return tab diff --git a/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift b/DuckDuckGoTests/NewTabPageControllerDaxDialogTests.swift similarity index 93% rename from DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift rename to DuckDuckGoTests/NewTabPageControllerDaxDialogTests.swift index 66c9506142..440a2934df 100644 --- a/DuckDuckGoTests/HomeViewControllerDaxDialogTests.swift +++ b/DuckDuckGoTests/NewTabPageControllerDaxDialogTests.swift @@ -1,5 +1,5 @@ // -// HomeViewControllerDaxDialogTests.swift +// NewTabPageControllerDaxDialogTests.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -26,12 +26,12 @@ import SwiftUI import Persistence import BrowserServicesKit -final class HomeViewControllerDaxDialogTests: XCTestCase { +final class NewTabPageControllerDaxDialogTests: XCTestCase { var variantManager: CapturingVariantManager! var dialogFactory: CapturingNewTabDaxDialogProvider! var specProvider: MockNewTabDialogSpecProvider! - var hvc: HomeViewController! + var hvc: NewTabPageViewController! override func setUpWithError() throws { let db = CoreDataDatabase.bookmarksMock @@ -56,19 +56,18 @@ final class HomeViewControllerDaxDialogTests: XCTestCase { remoteMessagingAvailabilityProvider: MockRemoteMessagingAvailabilityProviding(), duckPlayerStorage: MockDuckPlayerStorage()) let homePageConfiguration = HomePageConfiguration(remoteMessagingClient: remoteMessagingClient, privacyProDataReporter: MockPrivacyProDataReporter()) - let dependencies = HomePageDependencies( - homePageConfiguration: homePageConfiguration, - model: Tab(), - favoritesViewModel: MockFavoritesListInteracting(), - appSettings: AppSettingsMock(), + hvc = NewTabPageViewController( + tab: Tab(), + isNewTabPageCustomizationEnabled: false, + interactionModel: MockFavoritesListInteracting(), syncService: MockDDGSyncing(authState: .active, isSyncInProgress: false), - syncDataProviders: dataProviders, - privacyProDataReporter: MockPrivacyProDataReporter(), + syncBookmarksAdapter: dataProviders.bookmarksAdapter, + homePageMessagesConfiguration: homePageConfiguration, variantManager: variantManager, newTabDialogFactory: dialogFactory, - newTabDialogTypeProvider: specProvider) - hvc = HomeViewController.loadFromStoryboard( - homePageDependecies: dependencies) + newTabDialogTypeProvider: specProvider, + faviconLoader: EmptyFaviconLoading() + ) let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() diff --git a/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift b/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift index 09a02148ca..758ddde88e 100644 --- a/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift +++ b/DuckDuckGoTests/NewTabPageFavoritesModelTests.swift @@ -50,6 +50,13 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertEqual(PixelFiringMock.lastPixelName, Pixel.Event.newTabPageFavoritesSeeLess.name) } + func testReturnsAllFavoritesWhenCustomizationDisabled() { + favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + XCTAssertEqual(sut.prefixedFavorites(for: 1).items.count, 10) + } + func testFiresPixelsOnFavoriteSelected() { let sut = createSUT() @@ -99,6 +106,16 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertFalse(slice.isCollapsible) } + func testPrefixFavoritesDoesNotCreatePlaceholdersWhenCustomizationDisabled() { + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + let slice = sut.prefixedFavorites(for: 3) + + XCTAssertTrue(slice.items.filter(\.isPlaceholder).isEmpty) + XCTAssertTrue(slice.items.isEmpty) + XCTAssertFalse(slice.isCollapsible) + } + func testPrefixFavoritesLimitsToTwoRows() { favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) let sut = createSUT() @@ -109,6 +126,16 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertTrue(slice.isCollapsible) } + func testListNotCollapsibleWhenCustomizationDisabled() { + favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) + + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + let favorites = sut.prefixedFavorites(for: 1) + XCTAssertFalse(favorites.isCollapsible) + XCTAssertFalse(sut.isCollapsed) + } + func testAddItemIsLastWhenFavoritesPresent() throws { favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) let sut = createSUT() @@ -126,8 +153,19 @@ final class NewTabPageFavoritesModelTests: XCTestCase { XCTAssertTrue(firstItem == .addFavorite) } - private func createSUT() -> FavoritesViewModel { - FavoritesViewModel(favoriteDataSource: favoriteDataSource, + func testDoesNotAppendAddItemWhenCustomizationDisabled() { + let sut = createSUT(isNewTabPageCustomizationEnabled: false) + + XCTAssertNil(sut.allFavorites.first) + + favoriteDataSource.favorites.append(contentsOf: Array(repeating: Favorite.stub(), count: 10)) + + XCTAssertNil(sut.allFavorites.first(where: { $0 == .addFavorite })) + } + + private func createSUT(isNewTabPageCustomizationEnabled: Bool = true) -> FavoritesViewModel { + FavoritesViewModel(isNewTabPageCustomizationEnabled: isNewTabPageCustomizationEnabled, + favoriteDataSource: favoriteDataSource, faviconLoader: FavoritesFaviconLoader(), pixelFiring: PixelFiringMock.self, dailyPixelFiring: PixelFiringMock.self) diff --git a/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift b/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift index ad3c3be669..ff93a30198 100644 --- a/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift +++ b/DuckDuckGoTests/OnboardingDaxFavouritesTests.swift @@ -26,6 +26,7 @@ import BrowserServicesKit import RemoteMessaging import Configuration import Core +import SubscriptionTestingUtilities @testable import DuckDuckGo final class OnboardingDaxFavouritesTests: XCTestCase { @@ -78,7 +79,8 @@ final class OnboardingDaxFavouritesTests: XCTestCase { contextualOnboardingPixelReporter: OnboardingPixelReporterMock(), tutorialSettings: tutorialSettingsMock, subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock.enabled, - voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true) + voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true), + subscriptionCookieManager: SubscriptionCookieManagerMock() ) let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() diff --git a/DuckDuckGoTests/OnboardingManagerTests.swift b/DuckDuckGoTests/OnboardingManagerTests.swift index b0f90803d5..c5f0c2bff8 100644 --- a/DuckDuckGoTests/OnboardingManagerTests.swift +++ b/DuckDuckGoTests/OnboardingManagerTests.swift @@ -32,7 +32,7 @@ final class OnboardingManagerTests: XCTestCase { appSettingsMock = AppSettingsMock() featureFlaggerMock = MockFeatureFlagger() variantManagerMock = MockVariantManager() - sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock, variantManager: variantManagerMock) + sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock, variantManager: variantManagerMock, isIphone: true) } override func tearDownWithError() throws { @@ -43,45 +43,47 @@ final class OnboardingManagerTests: XCTestCase { try super.tearDownWithError() } - func testWhenIsLocalFlagEnabledIsCalledAndAppDefaultsOnboardingHiglightsEnabledIsTrueThenReturnTrue() { + // MARK: - Onboarding Highlights + + func testWhenIsOnboardingHighlightsLocalFlagEnabledCalledAndAppDefaultsOnboardingHiglightsEnabledIsTrueThenReturnTrue() { // GIVEN appSettingsMock.onboardingHighlightsEnabled = true // WHEN - let result = sut.isLocalFlagEnabled + let result = sut.isOnboardingHighlightsLocalFlagEnabled // THEN XCTAssertTrue(result) } - func testWhenIsLocalFlagEnabledIsCalledAndAppDefaultsOnboardingHiglightsEnabledIsFalseThenReturnFalse() { + func testWhenIsOnboardingHighlightsLocalFlagEnabledCalledAndAppDefaultsOnboardingHiglightsEnabledIsFalseThenReturnFalse() { // GIVEN appSettingsMock.onboardingHighlightsEnabled = false // WHEN - let result = sut.isLocalFlagEnabled + let result = sut.isOnboardingHighlightsLocalFlagEnabled // THEN XCTAssertFalse(result) } - func testWhenIsFeatureFlagEnabledIsCalledAndFeaturFlaggerFeatureIsOnThenReturnTrue() { + func testWhenIsOnboardingHighlightsFeatureFlagEnabledCalledAndFeaturFlaggerFeatureIsOnThenReturnTrue() { // GIVEN featureFlaggerMock.enabledFeatureFlags = [FeatureFlag.onboardingHighlights] // WHEN - let result = sut.isFeatureFlagEnabled + let result = sut.isOnboardingHighlightsFeatureFlagEnabled // THEN XCTAssertTrue(result) } - func testWhenIsFeatureFlagEnabledIsCalledAndFeaturFlaggerFeatureIsOffThenReturnFalse() { + func testWhenIsOnboardingHighlightsFeatureFlagEnabledCalledAndFeaturFlaggerFeatureIsOffThenReturnFalse() { // GIVEN featureFlaggerMock.enabledFeatureFlags = [] // WHEN - let result = sut.isFeatureFlagEnabled + let result = sut.isOnboardingHighlightsFeatureFlagEnabled // THEN XCTAssertFalse(result) @@ -150,4 +152,112 @@ final class OnboardingManagerTests: XCTestCase { // THEN XCTAssertFalse(result) } + + // MARK: - Add to Dock + + func testWhenIsAddToDockLocalFlagEnabledCalledAndAppDefaultsOnboardingAddToDockEnabledIsTrueThenReturnTrue() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = true + + // WHEN + let result = sut.isAddToDockLocalFlagEnabled + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsAddToDockLocalFlagEnabledCalledAndAppDefaultsOnboardingAddToDockEnabledIsFalseThenReturnFalse() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = false + + // WHEN + let result = sut.isAddToDockLocalFlagEnabled + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsAddToDockFeatureFlagEnabledCalledAndFeaturFlaggerFeatureIsOnThenReturnTrue() { + // GIVEN + featureFlaggerMock.enabledFeatureFlags = [FeatureFlag.onboardingAddToDock] + + // WHEN + let result = sut.isAddToDockFeatureFlagEnabled + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsAddToDockFeatureFlagEnabledCalledAndFeaturFlaggerFeatureIsOffThenReturnFalse() { + // GIVEN + featureFlaggerMock.enabledFeatureFlags = [] + + // WHEN + let result = sut.isAddToDockFeatureFlagEnabled + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsAddToDockEnabledCalledAndLocalFlagEnabledIsFalseAndFeatureFlagIsFalseThenReturnFalse() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = false + featureFlaggerMock.enabledFeatureFlags = [] + + // WHEN + let result = sut.isAddToDockEnabled + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsAddToDockEnabledCalledAndLocalFlagEnabledIsTrueAndFeatureFlagIsFalseThenReturnFalse() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = true + featureFlaggerMock.enabledFeatureFlags = [] + + // WHEN + let result = sut.isAddToDockEnabled + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsAddToDockEnabledCalledAndLocalFlagEnabledIsFalseAndFeatureFlagEnabledIsTrueThenReturnFalse() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = false + featureFlaggerMock.enabledFeatureFlags = [.onboardingAddToDock] + + // WHEN + let result = sut.isAddToDockEnabled + + // THEN + XCTAssertFalse(result) + } + + func testWhenIsAddToDockEnabledAndLocalFlagEnabledIsTrueAndFeatureFlagEnabledIsTrueThenReturnTrue() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = true + featureFlaggerMock.enabledFeatureFlags = [.onboardingAddToDock] + + // WHEN + let result = sut.isAddToDockEnabled + + // THEN + XCTAssertTrue(result) + } + + func testWhenIsAddToDockEnabledAndLocalAndFeatureFlagsAreEnabledAndDeviceIsIpadReturnFalse() { + // GIVEN + appSettingsMock.onboardingAddToDockEnabled = true + featureFlaggerMock.enabledFeatureFlags = [.onboardingAddToDock] + sut = OnboardingManager(appDefaults: appSettingsMock, featureFlagger: featureFlaggerMock, variantManager: variantManagerMock, isIphone: false) + + // WHEN + let result = sut.isAddToDockEnabled + + // THEN + XCTAssertFalse(result) + } + } diff --git a/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift b/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift index ca052c5717..7ccd6d49b9 100644 --- a/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift +++ b/DuckDuckGoTests/OnboardingNavigationDelegateTests.swift @@ -26,6 +26,7 @@ import BrowserServicesKit import RemoteMessaging import Configuration import Combine +import SubscriptionTestingUtilities @testable import DuckDuckGo @testable import Core @@ -76,7 +77,8 @@ final class OnboardingNavigationDelegateTests: XCTestCase { contextualOnboardingLogic: ContextualOnboardingLogicMock(), contextualOnboardingPixelReporter: onboardingPixelReporter, subscriptionFeatureAvailability: SubscriptionFeatureAvailabilityMock.enabled, - voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true)) + voiceSearchHelper: MockVoiceSearchHelper(isSpeechRecognizerAvailable: true, voiceSearchEnabled: true), + subscriptionCookieManager: SubscriptionCookieManagerMock()) let window = UIWindow(frame: UIScreen.main.bounds) window.rootViewController = UIViewController() window.makeKeyAndVisible() diff --git a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift index 49d41fd704..2d4979997f 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionContainerViewModelTests.swift @@ -24,7 +24,21 @@ import SubscriptionTestingUtilities final class SubscriptionContainerViewModelTests: XCTestCase { var sut: SubscriptionContainerViewModel! - let subscriptionManager = MockDependencyProvider().subscriptionManager + + let subscriptionManager: SubscriptionManager = { + let accountManager = AccountManagerMock() + let subscriptionService = SubscriptionEndpointServiceMock() + let authService = AuthEndpointServiceMock() + let storePurchaseManager = StorePurchaseManagerMock() + return SubscriptionManagerMock(accountManager: accountManager, + subscriptionEndpointService: subscriptionService, + authEndpointService: authService, + storePurchaseManager: storePurchaseManager, + currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, + purchasePlatform: .appStore), + canPurchase: true) + }() + let subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { diff --git a/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift index 166657aa67..67235b541e 100644 --- a/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift +++ b/DuckDuckGoTests/Subscription/SubscriptionFlowViewModelTests.swift @@ -25,7 +25,20 @@ import SubscriptionTestingUtilities final class SubscriptionFlowViewModelTests: XCTestCase { private var sut: SubscriptionFlowViewModel! - let subscriptionManager = MockDependencyProvider().subscriptionManager + let subscriptionManager: SubscriptionManager = { + let accountManager = AccountManagerMock() + let subscriptionService = DefaultSubscriptionEndpointService(currentServiceEnvironment: .production) + let authService = DefaultAuthEndpointService(currentServiceEnvironment: .production) + let storePurchaseManager = DefaultStorePurchaseManager() + return SubscriptionManagerMock(accountManager: accountManager, + subscriptionEndpointService: subscriptionService, + authEndpointService: authService, + storePurchaseManager: storePurchaseManager, + currentEnvironment: SubscriptionEnvironment(serviceEnvironment: .production, + purchasePlatform: .appStore), + canPurchase: true) + }() + let subscriptionFeatureAvailability = SubscriptionFeatureAvailabilityMock.enabled func testWhenInitWithOriginThenSubscriptionFlowPurchaseURLHasOriginSet() { diff --git a/DuckDuckGoTests/VideoPlayerViewModelTests.swift b/DuckDuckGoTests/VideoPlayerViewModelTests.swift new file mode 100644 index 0000000000..bb5a78947d --- /dev/null +++ b/DuckDuckGoTests/VideoPlayerViewModelTests.swift @@ -0,0 +1,137 @@ +// +// VideoPlayerViewModelTests.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 AVFoundation +@testable import DuckDuckGo + +final class VideoPlayerViewModelTests: XCTestCase { + private let fakeURL = URL(string: "https://duckduckgo.com") + private var sut: VideoPlayerViewModel! + private var mockPlayer: MockAVQueuePlayer! + + @MainActor + override func setUpWithError() throws { + try super.setUpWithError() + let url = try XCTUnwrap(fakeURL) + mockPlayer = .init() + sut = VideoPlayerViewModel(url: url, loopVideo: false, player: mockPlayer) + } + + override func tearDownWithError() throws { + sut = nil + try super.tearDownWithError() + } + + @MainActor + func testWhenInitWithURLThenPlayerItemIsAssignedToPlayer() throws { + // GIVEN + let url = try XCTUnwrap(fakeURL) + mockPlayer = .init() + XCTAssertFalse(mockPlayer.didCallReplaceCurrentItem) + XCTAssertNil(mockPlayer.capturedCurrentItem) + + // WHEN + sut = VideoPlayerViewModel(url: url, loopVideo: true, player: mockPlayer) + + // THEN + let asset = try XCTUnwrap(mockPlayer.capturedCurrentItem?.asset as? AVURLAsset) + XCTAssertTrue(mockPlayer.didCallReplaceCurrentItem) + XCTAssertEqual(asset.url, url) + } + + @MainActor + func testWhenIsLoopingVideoCalledAndLoopVideoIsTrueThenReturnTrue() throws { + // GIVEN + let url = try XCTUnwrap(fakeURL) + mockPlayer = .init() + sut = VideoPlayerViewModel(url: url, loopVideo: true, player: mockPlayer) + + // WHEN + let result = sut.isLoopingVideo + + // THEN + XCTAssertTrue(result) + } + + @MainActor + func testWhenIsLoopingVideoCalledAndLoopVideoIsFalseThenReturnFalse() throws { + // GIVEN + let url = try XCTUnwrap(fakeURL) + mockPlayer = .init() + sut = VideoPlayerViewModel(url: url, loopVideo: false, player: mockPlayer) + + // WHEN + let result = sut.isLoopingVideo + + // THEN + XCTAssertFalse(result) + } + + func testWhenInitThenVideoPlayerConfigurationIsTheOneExpected() throws { + // THEN + XCTAssertFalse(mockPlayer.preventsDisplaySleepDuringVideoPlayback) + XCTAssertFalse(mockPlayer.allowsExternalPlayback) + } + + @MainActor + func testWhenPlayIsCalledThenAskPlayerToPlay() { + // GIVEN + XCTAssertFalse(mockPlayer.didCallPlay) + + // WHEN + sut.play() + + // THEN + XCTAssertTrue(mockPlayer.didCallPlay) + } + + @MainActor + func testWhenPauseIsCalledThenAskPlayerToPause() { + // GIVEN + XCTAssertFalse(mockPlayer.didCallPause) + + // WHEN + sut.pause() + + // THEN + XCTAssertTrue(mockPlayer.didCallPause) + } + +} + +final class MockAVQueuePlayer: AVQueuePlayer { + private(set) var didCallPlay = false + private(set) var didCallPause = false + private(set) var didCallReplaceCurrentItem = false + private(set) var capturedCurrentItem: AVPlayerItem? + + override func replaceCurrentItem(with item: AVPlayerItem?) { + didCallReplaceCurrentItem = true + self.capturedCurrentItem = item + } + + override func play() { + didCallPlay = true + } + + override func pause() { + didCallPause = true + } +} diff --git a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift index 3b50f07aef..4b968fcd68 100644 --- a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift +++ b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift @@ -19,490 +19,567 @@ import XCTest import WebKit -import ContentScopeScripts import Combine import BrowserServicesKit - -class DuckPlayerExperimentMock: DuckPlayerLaunchExperimentHandling { - var duckPlayerMode: DuckDuckGo.DuckPlayerMode? - var isEnrolled = true - var isExperimentCohort = true - func assignUserToCohort() {} - func fireSearchPixels() {} - func fireYoutubePixel(videoID: String) {} -} +import Core @testable import DuckDuckGo class DuckPlayerNavigationHandlerTests: XCTestCase { - - var webView: WKWebView! + var mockWebView: MockWebView! - var mockNavigationDelegate: MockWKNavigationDelegate! var mockAppSettings: AppSettingsMock! var mockPrivacyConfig: PrivacyConfigurationManagerMock! var playerSettings: MockDuckPlayerSettings! var player: MockDuckPlayer! - var featureFlagger: FeatureFlagger! - + var featureFlagger: MockDuckPlayerFeatureFlagger! + var handler: DuckPlayerNavigationHandler! + var tabNavigator: MockDuckPlayerTabNavigator! + override func setUp() { super.setUp() - webView = WKWebView() mockWebView = MockWebView() - mockNavigationDelegate = MockWKNavigationDelegate() mockAppSettings = AppSettingsMock() + mockAppSettings.duckPlayerOpenInNewTab = false // Disable openInNewTab mockPrivacyConfig = PrivacyConfigurationManagerMock() + playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, + privacyConfigManager: mockPrivacyConfig) featureFlagger = MockDuckPlayerFeatureFlagger() - webView.navigationDelegate = mockNavigationDelegate - + player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) + + // Create and assign the mock tab navigator + tabNavigator = MockDuckPlayerTabNavigator() + + handler = DuckPlayerNavigationHandler(duckPlayer: player, + featureFlagger: featureFlagger, + appSettings: mockAppSettings, + pixelFiring: PixelFiringMock.self, + dailyPixelFiring: PixelFiringMock.self) + + // Inject the mock tab navigator + handler.tabNavigationHandler = tabNavigator + + PixelFiringMock.tearDown() } - + override func tearDown() { - webView.navigationDelegate = nil mockWebView = nil - mockNavigationDelegate = nil + mockAppSettings = nil + mockPrivacyConfig = nil + playerSettings = nil + player = nil + featureFlagger = nil + tabNavigator = nil + handler = nil + PixelFiringMock.tearDown() super.tearDown() } - - // Test for htmlTemplatePath existence - func testHtmlTemplatePathExists() { - let templatePath = DuckPlayerNavigationHandler.htmlTemplatePath - let fileExists = FileManager.default.fileExists(atPath: templatePath) - XCTAssertFalse(templatePath.isEmpty, "The template path should not be empty") - XCTAssertTrue(fileExists, "The template file should exist at the specified path") - } - - // Test for makeDuckPlayerRequest(from:) - func testMakeDuckPlayerRequestFromOriginalRequest() { - let originalRequest = URLRequest(url: URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")!) - - let duckPlayerRequest = DuckPlayerNavigationHandler.makeDuckPlayerRequest(from: originalRequest) - - XCTAssertEqual(duckPlayerRequest.url?.host, "www.youtube-nocookie.com") - XCTAssertEqual(duckPlayerRequest.url?.path, "/embed/abc123") - XCTAssertEqual(duckPlayerRequest.url?.query?.contains("t=10s"), true) - XCTAssertEqual(duckPlayerRequest.value(forHTTPHeaderField: "Referer"), "http://localhost") - XCTAssertEqual(duckPlayerRequest.httpMethod, "GET") - } - - // Test for makeDuckPlayerRequest(for:timestamp:) - func testMakeDuckPlayerRequestForVideoID() { - let videoID = "abc123" - let timestamp = "10s" - - let duckPlayerRequest = DuckPlayerNavigationHandler.makeDuckPlayerRequest(for: videoID, timestamp: timestamp) - - XCTAssertEqual(duckPlayerRequest.url?.host, "www.youtube-nocookie.com") - XCTAssertEqual(duckPlayerRequest.url?.path, "/embed/abc123") - XCTAssertEqual(duckPlayerRequest.url?.query?.contains("t=10s"), true) - XCTAssertEqual(duckPlayerRequest.value(forHTTPHeaderField: "Referer"), "http://localhost") - XCTAssertEqual(duckPlayerRequest.httpMethod, "GET") - } - - // Test for makeHTMLFromTemplate - func testMakeHTMLFromTemplate() { - let expectedHtml = try? String(contentsOfFile: DuckPlayerNavigationHandler.htmlTemplatePath) - let html = DuckPlayerNavigationHandler.makeHTMLFromTemplate() - XCTAssertEqual(html, expectedHtml) + + // MARK: - Test Cases + + @MainActor + func testHandleNavigation_MultipleCalls_WithinHalfSecond_OnlyProcessesFirst() async { + // Arrange + let youtubeURL1 = URL(string: "https://www.youtube.com/watch?v=abc123")! + let youtubeURL2 = URL(string: "https://www.youtube.com/watch?v=def456")! + let youtubeURL3 = URL(string: "https://www.youtube.com/watch?v=ghi789")! + let navigationAction1 = MockNavigationAction(request: URLRequest(url: youtubeURL1)) + let navigationAction2 = MockNavigationAction(request: URLRequest(url: youtubeURL2)) + let navigationAction3 = MockNavigationAction(request: URLRequest(url: youtubeURL3)) + playerSettings.mode = .enabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + mockWebView.loadedRequests = [] + mockWebView.loadCallCount = 0 + + // Act + handler.handleDuckNavigation(navigationAction1, webView: mockWebView) + handler.handleDuckNavigation(navigationAction2, webView: mockWebView) + handler.handleDuckNavigation(navigationAction3, webView: mockWebView) + + // Wait for a short time to allow any asynchronous processing + try? await Task.sleep(nanoseconds: 100_000_000) + + // Assert + XCTAssertEqual(mockWebView.loadCallCount, 1, "Expected only one request to be loaded") + if let loadedRequest = mockWebView.lastLoadedRequest { + XCTAssertEqual(loadedRequest.url?.scheme, "duck") + XCTAssertEqual(loadedRequest.url?.host, "player") + XCTAssertEqual(loadedRequest.url?.path, "/abc123") + } else { + XCTFail("DuckPlayer was not loaded") + } } - - - // MARK: - Decide policyFor Tests - - + @MainActor - func testDecidePolicyForVideoWasAlreadyHandled() { - - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let expectation = self.expectation(description: "Completion handler called") - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var navigationPolicy: WKNavigationActionPolicy? - - handler.lastHandledVideoID = "abc123" - - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) - - waitForExpectations(timeout: 1, handler: nil) - - XCTAssertEqual(navigationPolicy, .cancel, "Expected navigation policy to be .cancel") + func testHandleNavigation_DuckPlayerModeDisabled_AlwaysRedirectsToYouTube() async { + // Arrange + let duckPlayerURL = URL(string: "duck://player/123123")! + let navigationAction = MockNavigationAction(request: URLRequest(url: duckPlayerURL)) + playerSettings.mode = .disabled // DuckPlayer mode is disabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] // Feature is enabled + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + + // Assert + // It should redirect to YouTube + if let loadedRequest = mockWebView.lastLoadedRequest { + XCTAssertEqual(loadedRequest.url?.host, "m.youtube.com") + XCTAssertEqual(loadedRequest.url?.path, "/watch") + XCTAssertTrue(loadedRequest.url?.query?.contains("v=123123") ?? false) + } else { + XCTFail("YouTube was not loaded") + } } @MainActor - func testDecidePolicyForVideosThatShouldLoadInYoutube() { - - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s&embeds_referring_euri=somevalue")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let expectation = self.expectation(description: "Completion handler called") - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var navigationPolicy: WKNavigationActionPolicy? - - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) - - waitForExpectations(timeout: 1, handler: nil) - - XCTAssertEqual(navigationPolicy, .allow, "Expected navigation policy to be .allow") + func testHandleNavigation_OpenInYouTubeURL_AlwaysRedirectsToYouTube() async { + // Arrange + let openInYouTubeURL = URL(string: "duck://player/openInYoutube?v=12311")! + let navigationAction = MockNavigationAction(request: URLRequest(url: openInYouTubeURL)) + playerSettings.mode = .enabled // DuckPlayer mode is enabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] // Feature is enabled + + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + // Assert + if let openedURL = tabNavigator.openedURL { + XCTAssertEqual(openedURL.host, "m.youtube.com") + XCTAssertEqual(openedURL.path, "/watch") + XCTAssertTrue(openedURL.query?.contains("v=12311") ?? false) + } else { + XCTFail("No URL was opened in a new tab") + } } - + @MainActor - func testDecidePolicyForVideosThatShouldLoadInDuckPlayer() { - - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! + func testHandleNavigation_YouTubeURL_WithWatchInYouTubeParameter_RedirectsToYouTube() async { + // Arrange + let youtubeURL = URL(string: "https://m.youtube.com/watch?v=abc123&embeds_referring_euri=true")! let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let expectation = self.expectation(description: "Completion handler called") - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var navigationPolicy: WKNavigationActionPolicy? - - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) - - waitForExpectations(timeout: 1, handler: nil) - - XCTAssertEqual(navigationPolicy, .cancel, "Expected navigation policy to be .cancel") + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + // Assert + // It should redirect to YouTube + if let loadedRequest = mockWebView.lastLoadedRequest { + XCTAssertEqual(loadedRequest.url?.host, "m.youtube.com") + XCTAssertEqual(loadedRequest.url?.path, "/watch") + XCTAssertTrue(loadedRequest.url?.query?.contains("v=abc123") ?? false) + } else { + XCTFail("YouTube was not loaded") + } } - + @MainActor - func testDecidePolicyForOtherURLThatShouldLoadNormally() { - - let youtubeURL = URL(string: "https://www.google.com/")! + func testHandleNavigation_YouTubeURL_WithoutWatchInYouTubeParameter_RedirectsToDuckPlayer() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let expectation = self.expectation(description: "Completion handler called") - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var navigationPolicy: WKNavigationActionPolicy? - - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) - - waitForExpectations(timeout: 1, handler: nil) - - XCTAssertEqual(navigationPolicy, .allow, "Expected navigation policy to be .allow") + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + if let loadedRequest = mockWebView.lastLoadedRequest { + XCTAssertEqual(loadedRequest.url?.scheme, "duck") + XCTAssertEqual(loadedRequest.url?.host, "player") + XCTAssertEqual(loadedRequest.url?.path, "/abc123") + XCTAssertTrue(loadedRequest.url?.query?.contains("referrer=other") ?? false) + } else { + XCTFail("DuckPlayer was not loaded") + } } - // MARK: - HandleJS Navigation Tests - @MainActor - func testJSNavigationForVideoWasAlreadyHandled() { - - let url: URL = URL(string: "https://www.example.com/")! - webView.load(URLRequest(url: url)) - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.lastHandledVideoID = "abc123" - handler.handleJSNavigation(url: youtubeURL, webView: webView) - - XCTAssertEqual(webView.url?.absoluteString, url.absoluteString) + func testHandleNavigation_ToDirectDuckURLInNewTab_OpenInNewTabWithParameters() async { + // Arrange + let youtubeURL = URL(string: "duck://player/abc123")! + let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) + playerSettings.mode = .enabled + playerSettings.openInNewTab = true + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + + // Assert + if let openedURL = tabNavigator.openedURL { + XCTAssertEqual(openedURL.host, "player") + XCTAssertEqual(openedURL.path, "/abc123") + XCTAssertTrue(openedURL.query?.contains("referrer=other") ?? false) + } else { + XCTFail("No URL was opened in a new tab") + } } @MainActor - func testJSNavigationForVideoThatShouldLoadInYoutube() { - - let url: URL = URL(string: "https://www.example.com/")! - webView.load(URLRequest(url: url)) - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s&embeds_referring_euri=somevalue")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.handleJSNavigation(url: youtubeURL, webView: webView) - - XCTAssertEqual(webView.url?.absoluteString, url.absoluteString) + func testHandleNavigation_ToYouTubeWatchInAskMode_RedirectsToDuckPlayerWithParameters() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) + playerSettings.mode = .alwaysAsk + playerSettings.openInNewTab = true + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + + // Assert + if let openedURL = tabNavigator.openedURL { + XCTAssertEqual(openedURL.host, "player") + XCTAssertEqual(openedURL.path, "/abc123") + XCTAssertTrue(openedURL.query?.contains("referrer=other") ?? false) + } else { + XCTFail("No URL was opened in a new tab") + } } @MainActor - func testJSNavigationForVideoThatShouldLoadInDuckPlayer() { - - let url: URL = URL(string: "https://www.example.com/")! - webView.load(URLRequest(url: url)) - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + func testHandleDelegateNavigation_DuckPlayerURL_CancelNavigationAndLoadsDuckPlayerWithParamsInTab() async { + // Arrange + let duckPlayerURL = URL(string: "duck://player/abc123")! + let request = URLRequest(url: duckPlayerURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.handleJSNavigation(url: youtubeURL, webView: webView) - - XCTAssertEqual(webView.url?.absoluteString, "duck://player/abc123?t=10s") + playerSettings.openInNewTab = true + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + handler.handleDuckNavigation(navigationAction, webView: mockWebView) + + // Assert + if let openedURL = tabNavigator.openedURL { + XCTAssertEqual(openedURL.host, "player") + XCTAssertEqual(openedURL.path, "/abc123") + XCTAssertTrue(openedURL.query?.contains("referrer=other") ?? false) + } else { + XCTFail("No URL was opened in a new tab") + } } - // MARK: Handle Navigation Tests - + // MARK: - Tests for handleURLChange + @MainActor - func testAgeRestrictedVideoShouldNotBeHandled() { - - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s&embeds_referring_euri=somevalue")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + func testHandleURLChange_MultipleCallsWithinOneSecond_ReturnsDuplicateNavigation() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + mockWebView.setCurrentURL(youtubeURL) playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let result1 = handler.handleURLChange(webView: mockWebView) - handler.handleNavigation(navigationAction, webView: webView) - XCTAssertEqual(webView.url, nil) + // Wait less than one second before calling again + try? await Task.sleep(nanoseconds: 500_000_000) + let result2 = handler.handleURLChange(webView: mockWebView) + + // Assert + if case .handled = result1 { + // Success + } else { + XCTFail("Expected first call to return .handled") + } + + if case .notHandled(.duplicateNavigation) = result2 { + // Success + } else { + XCTFail("Expected second call to return .duplicateNavigation") + } } - + @MainActor - func testHandleNavigationLoadsDuckPlayer() { - - let link = URL(string: "duck://player/12345")! - let navigationAction = MockNavigationAction(request: URLRequest(url: link)) - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + func testHandleURLChange_DuckPlayerURL_ReturnsHandled() async { + // Arrange + let duckPlayerURL = URL(string: "duck://player/abc123")! + mockWebView.setCurrentURL(duckPlayerURL) playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.handleNavigation(navigationAction, webView: webView) - - let expectation = self.expectation(description: "Simulated Request Expectation") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - - XCTAssertEqual(self.webView.url?.absoluteString, "https://www.youtube-nocookie.com/embed/12345") - expectation.fulfill() + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let result = handler.handleURLChange(webView: mockWebView) + + if case .notHandled(.isNotYoutubeWatch) = result { + // Success + } else { + XCTFail("Expected .unhandled for duck:// URLs") } - - waitForExpectations(timeout: 1.0, handler: nil) - } - + @MainActor - func testHandleNavigationWithDuckPlayerDisabledRedirectsToYoutube() { - - let link = URL(string: "duck://player/12345")! - let navigationAction = MockNavigationAction(request: URLRequest(url: link)) - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.mode = .disabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.handleNavigation(navigationAction, webView: webView) - - let expectation = self.expectation(description: "Youtube URL request") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - - guard let redirectedURL = self.webView.url, - let components = URLComponents(url: redirectedURL, resolvingAgainstBaseURL: false) else { - XCTFail("URL is missing or could not be parsed.") - expectation.fulfill() - return - } - - // Extract path and video ID from the redirected URL - let isWatchPath = components.path == "/watch" - let videoID = components.queryItems?.first(where: { $0.name == "v" })?.value - - XCTAssertTrue(isWatchPath, "Expected the path to be /watch.") - XCTAssertEqual(videoID, "12345", "Expected the video ID to match.") - expectation.fulfill() + func testHandleURLChange_NonYouTubeURL_ReturnsUnhandled() async { + // Arrange + let nonYouTubeURL = URL(string: "https://www.example.com")! + mockWebView.setCurrentURL(nonYouTubeURL) + playerSettings.mode = .enabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let result = handler.handleURLChange(webView: mockWebView) + + // Assert + if case .notHandled(.invalidURL) = result { + // Success + } else { + XCTFail("Expected .unhandled for non-YouTube URL") } - - waitForExpectations(timeout: 1.0, handler: nil) } - + @MainActor - func testHandleReloadForDuckPlayerVideo() { - let duckPlayerURL = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - handler.handleReload(webView: mockWebView) - - if let loadedRequest = mockWebView.lastLoadedRequest { - XCTAssertEqual(loadedRequest.url?.scheme, "duck") - XCTAssertEqual(loadedRequest.url?.host, "player") - XCTAssertEqual(loadedRequest.url?.path, "/abc123") - XCTAssertEqual(loadedRequest.url?.query?.contains("t=10s"), true) + func testHandleURLChange_YouTubeURL_DuckPlayerModeDisabled_ReturnsUnhandled() async { + // Arrange + let youtubeURL = URL(string: "https://m.youtube.com/watch?v=abc123")! + mockWebView.setCurrentURL(youtubeURL) + playerSettings.mode = .disabled // DuckPlayer mode is disabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let result = handler.handleURLChange(webView: mockWebView) + + // Assert + if case .notHandled(.duckPlayerDisabled) = result { + // Success + } else { + XCTFail("Expected .unhandled when DuckPlayer mode is disabled") } } - + @MainActor - func testAttach() { - let duckPlayerURL = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + func testHandleURLChange_YouTubeURL_FeatureFlagDisabled_ReturnsUnhandled() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + mockWebView.setCurrentURL(youtubeURL) playerSettings.mode = .enabled - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - handler.handleAttach(webView: mockWebView) - - if let loadedRequest = mockWebView.lastLoadedRequest { - XCTAssertEqual(loadedRequest.url?.scheme, "duck") - XCTAssertEqual(loadedRequest.url?.host, "player") - XCTAssertEqual(loadedRequest.url?.path, "/abc123") - XCTAssertEqual(loadedRequest.url?.query?.contains("t=10s"), true) + featureFlagger.enabledFeatures = [] // Feature is disabled + + // Act + let result = handler.handleURLChange(webView: mockWebView) + + // Assert + if case .notHandled(.featureOff) = result { + // Success + } else { + XCTFail("Expected .unhandled when feature flag is disabled") } } - func testGetURLForYoutubeNoCookieWithDuckPlayerEnabled() { - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + @MainActor + func testHandleDelegateNavigation_NotToMainFrame_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let navigationAction = MockNavigationAction(request: request) playerSettings.mode = .enabled - - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var url = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - var duckURL = handler.getDuckURLFor(url).absoluteString - XCTAssertEqual(duckURL, "duck://player/abc123?t=10s") - - url = URL(string: "https://www.youtube.com/watch?v=I9J120SZT14")! - duckURL = handler.getDuckURLFor(url).absoluteString - XCTAssertEqual(duckURL, url.absoluteString) - + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation NOT to be cancelled as it's not mainframe navigation") } - func testGetURLForYoutubeNoCookieWithDuckPlayerAskMode() { - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.mode = .alwaysAsk - - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var url = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - var duckURL = handler.getDuckURLFor(url).absoluteString - XCTAssertEqual(duckURL, "duck://player/abc123?t=10s") - - url = URL(string: "https://www.youtube.com/watch?v=I9J120SZT14")! - duckURL = handler.getDuckURLFor(url).absoluteString - XCTAssertEqual(duckURL, url.absoluteString) - + @MainActor + func testHandleDelegateNavigation_With_DuckPlayerFeatureDisabled_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation NOT be cancelled as DuckPlayer Feature is Disabled") } - func testGetURLForYoutubeNoCookieWithDuckPlayerDisabled() { - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + + @MainActor + func testHandleDelegateNavigation_With_DuckPlayerDisabled_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) playerSettings.mode = .disabled - - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - var url = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - var duckURL = handler.getDuckURLFor(url).absoluteString - XCTAssertEqual(duckURL, url.absoluteString) - - url = URL(string: "https://www.youtube.com/watch?v=I9J120SZT14")! - duckURL = handler.getDuckURLFor(url).absoluteString - XCTAssertEqual(duckURL, url.absoluteString) - + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation NOT to be cancelled as DuckPlayer is Disabled") } - func testShouldOpenInNewTabWhenEnabled() { - let youtubeURL = URL(string: "duck://player/abc123")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - - mockAppSettings.duckPlayerOpenInNewTab = true - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.navigationType = .linkActivated + @MainActor + func testHandleDelegateNavigation_ToYouTubeWith_DuckPlayerAlwaysAsk_ReturnsTrue() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) + playerSettings.mode = .alwaysAsk + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertTrue(shouldCancel, "Expected navigation TO be cancelled as it should redirect to Youtube") + } + + @MainActor + func testHandleDelegateNavigation_WithBackForwardNavigation_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, navigationType: .backForward, targetFrame: mockFrameInfo) playerSettings.mode = .enabled - - XCTAssertTrue(handler.shouldOpenInNewTab(navigationAction, webView: webView)) + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation to be cancelled as Nav is backForward") } - func testShouldNotOpenInNewTabWhenNotDuckPlayerURL() { - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=I9J120SZT14")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - - mockAppSettings.duckPlayerOpenInNewTab = true - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.navigationType = .linkActivated + @MainActor + func testHandleDelegateNavigation_WithAllowFirstVideo_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) playerSettings.mode = .enabled - - XCTAssertFalse(handler.shouldOpenInNewTab(navigationAction, webView: webView)) + playerSettings.allowFirstVideo = true + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation to be cancelled as it's first video") } - func testShouldNotOpenInNewTabWhenDisabled() { - let youtubeURL = URL(string: "duck://player/abc123")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) + @MainActor + func testHandleDelegateNavigation_WithValidURL_ReturnsTrue() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) + playerSettings.mode = .enabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertTrue(shouldCancel, "Expected navigation to be cancelled as it's first video") - mockAppSettings.duckPlayerOpenInNewTab = false + if let url = mockWebView.lastLoadedRequest?.url { + XCTAssertTrue(url.isDuckPlayer, "Expected final URL to be a Duck URL") + } else { + XCTFail("No URL was loaded. Expecting a Duck Player Video") + } - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.navigationType = .linkActivated + } + + + @MainActor + func testHandleDelegateNavigation_YouTubeURL_DuckPlayerDisabled_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let navigationAction = MockNavigationAction(request: request) + playerSettings.mode = .disabled // DuckPlayer mode is disabled + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation not to be cancelled when DuckPlayer is disabled") + } + + @MainActor + func testHandleDelegateNavigation_YouTubeURL_FeatureFlagDisabled_ReturnsFalse() async { + // Arrange + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + let request = URLRequest(url: youtubeURL) + let navigationAction = MockNavigationAction(request: request) playerSettings.mode = .enabled - - XCTAssertFalse(handler.shouldOpenInNewTab(navigationAction, webView: webView)) + featureFlagger.enabledFeatures = [] // Feature is disabled + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation not to be cancelled when feature flag is disabled") } - func testHandleJSNavigationEventWhenEnabled() { - let youtubeURL = URL(string: "duck://player/abc123")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - + @MainActor + func testHandleDelegateNavigation_DuckPlayerURL_DoesNotOpenInNewTabIfFeatureDisabled() async { + // Arrange + let duckPlayerURL = URL(string: "duck://player/abc123")! + let request = URLRequest(url: duckPlayerURL) + let mockFrameInfo = MockFrameInfo(isMainFrame: true) + let navigationAction = MockNavigationAction(request: request, targetFrame: mockFrameInfo) playerSettings.mode = .enabled - mockAppSettings.duckPlayerOpenInNewTab = true - - handler.handleEvent(event: .JSTriggeredNavigation, url: youtubeURL, navigationAction: nil) - - XCTAssertTrue(handler.navigationType == .linkActivated) + playerSettings.openInNewTab = true + featureFlagger.enabledFeatures = [.duckPlayer] // Duckplayer feature is disabled + + // Act + let shouldCancel = handler.handleDelegateNavigation(navigationAction: navigationAction, webView: mockWebView) + + // Assert + XCTAssertFalse(shouldCancel, "Expected navigation not to be cancelled for DuckPlayer URL") + XCTAssertNil(tabNavigator.openedURL, "No new tabs should open") } - func testHandleJSNavigationEventWhenDisabled() { - let youtubeURL = URL(string: "duck://player/abc123")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - + + // MARK: Reload Operations + @MainActor + func testHandleDelegateNavigation_DuckPlayerURLReloads_DoesNotOpenInANewTab() async { + // Arrange + let duckPlayerURL = URL(string: "duck://player/abc123")! playerSettings.mode = .enabled - mockAppSettings.duckPlayerOpenInNewTab = false + playerSettings.openInNewTab = true + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + mockWebView.setCurrentURL(duckPlayerURL) - handler.handleEvent(event: .JSTriggeredNavigation, url: youtubeURL, navigationAction: nil) - - XCTAssertFalse(handler.navigationType == .linkActivated) + // Act + handler.handleReload(webView: mockWebView) + + // Assert + XCTAssertNil(tabNavigator.openedURL, "No new tabs should open") } - func testHandleJSNavigationEventWhenDuckPlayerDisabled() { - let youtubeURL = URL(string: "duck://player/abc123")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - let player = MockDuckPlayer(settings: playerSettings, featureFlagger: featureFlagger) - let handler = DuckPlayerNavigationHandler(duckPlayer: player, featureFlagger: featureFlagger, appSettings: mockAppSettings, experiment: DuckPlayerExperimentMock()) - - handler.navigationType = .linkActivated - playerSettings.mode = .disabled - mockAppSettings.duckPlayerOpenInNewTab = true - - handler.handleEvent(event: .JSTriggeredNavigation, url: youtubeURL, navigationAction: nil) + @MainActor + func testHandleDelegateNavigation_YoutubeWatchURLWithAlwaysAsk_DoesNotOpenInANewTab() async { + // Arrange + let duckPlayerURL = URL(string: "https://www.youtube.com/watch?v=abc123")! + playerSettings.mode = .alwaysAsk + playerSettings.openInNewTab = true + featureFlagger.enabledFeatures = [.duckPlayer, .duckPlayerOpenInNewTab] + mockWebView.setCurrentURL(duckPlayerURL) - XCTAssertFalse(handler.navigationType == .linkActivated) - } + // Act + handler.handleReload(webView: mockWebView) + // Assert + XCTAssertNil(tabNavigator.openedURL, "No new tabs should open") + } + } diff --git a/FingerprintingUITests/FingerprintUITest.swift b/FingerprintingUITests/FingerprintUITest.swift index bd5c5a869c..e67ce7e248 100644 --- a/FingerprintingUITests/FingerprintUITest.swift +++ b/FingerprintingUITests/FingerprintUITest.swift @@ -88,8 +88,8 @@ class FingerprintUITest: XCTestCase { } let tablesQuery = app.tables - _ = tablesQuery.staticTexts["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: 25) - tablesQuery.staticTexts["DuckDuckGo — Privacy, simplified."].swipeLeft() + _ = tablesQuery.staticTexts["DuckDuckGo — Your protection, our priority."].waitForExistence(timeout: 25) + tablesQuery.staticTexts["DuckDuckGo — Your protection, our priority."].swipeLeft() tablesQuery.buttons["Delete"].tap() app.navigationBars["Bookmarks"].buttons["Done"].tap() } @@ -110,8 +110,8 @@ class FingerprintUITest: XCTestCase { let bookmarksToolbarButtons = app.toolbars.buttons _ = bookmarksToolbarButtons["Edit"].waitForExistence(timeout: 25) bookmarksToolbarButtons["Edit"].tap() - if app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: 25) { - app.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + if app.tables.staticTexts["DuckDuckGo — Your protection, our priority."].waitForExistence(timeout: 25) { + app.staticTexts["DuckDuckGo — Your protection, our priority."].tap() } else { XCTFail("Could not find bookmark") } @@ -146,8 +146,8 @@ class FingerprintUITest: XCTestCase { } else { XCTFail("Bookmarks button missing") } - if app.tables.staticTexts["DuckDuckGo — Privacy, simplified."].waitForExistence(timeout: 25) { - app.staticTexts["DuckDuckGo — Privacy, simplified."].tap() + if app.tables.staticTexts["DuckDuckGo — Your protection, our priority."].waitForExistence(timeout: 25) { + app.staticTexts["DuckDuckGo — Your protection, our priority."].tap() } else { XCTFail("Could not find bookmark") } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index f18657fd83..1d0d12d932 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -168,6 +168,32 @@ lane :alpha_adhoc do |options| end end + + +desc 'Promotes the latest TestFlight build to App Store without submitting for review' +lane :promote_latest_testflight_to_appstore do |options| + + app_identifier = options[:alpha] ? "com.duckduckgo.mobile.ios.alpha" : "com.duckduckgo.mobile.ios" + + latest_testflight_build_number( + api_key: get_api_key, + username: get_username(options), + platform: 'ios', + app_identifier: app_identifier + ) + + latest_build_number = lane_context[SharedValues::LATEST_TESTFLIGHT_BUILD_NUMBER] + latest_build_version = lane_context[SharedValues::LATEST_TESTFLIGHT_VERSION] + + UI.message("The latest build number #{latest_build_number} of the latest version: #{latest_build_version} for app identifier: #{app_identifier}") + + upload_metadata({ + build_number: latest_build_number.to_s, + app_version: latest_build_version.to_s, + app_identifier: app_identifier + }) +end + desc 'Makes App Store release build and uploads it to App Store Connect' lane :release_appstore do |options| build_release(options) @@ -194,7 +220,8 @@ desc 'Updates App Store metadata' lane :upload_metadata do |options| deliver(common_deliver_arguments.merge(options).merge({ skip_binary_upload: true, - skip_metadata: false + skip_metadata: false, + version_check_wait_retry_limit: 1 })) end diff --git a/package-lock.json b/package-lock.json index 0b5bb501b7..e61ff6afe9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "ios", "version": "1.0.0", "dependencies": { - "@duckduckgo/autoconsent": "^10.16.0" + "@duckduckgo/autoconsent": "^10.17.0" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", @@ -121,9 +121,10 @@ } }, "node_modules/@duckduckgo/autoconsent": { - "version": "10.16.0", - "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.16.0.tgz", - "integrity": "sha512-731vJAQ/wOu+zpGOgXgnuTyhpF7LCuiVmmOFVLUU9RNHX1357aEm+7D2SdyPAuHj+uDpGkhTbaz/MPX6qFoInQ==", + "version": "10.17.0", + "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.17.0.tgz", + "integrity": "sha512-zMB4BE5fpiqvjXPA0k8bCorWgh6eFMlkedRfuRVQYhbWqwLgrnsA7lv4U0ORTIJkvbBjABuYaprwr1yd/15D/w==", + "license": "MPL-2.0", "dependencies": { "tldts-experimental": "^6.1.37" } diff --git a/package.json b/package.json index 40af6c3a92..7cb35376c5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,6 @@ "rollup-plugin-terser": "^7.0.2" }, "dependencies": { - "@duckduckgo/autoconsent": "^10.16.0" + "@duckduckgo/autoconsent": "^10.17.0" } } diff --git a/scripts/prepare_release.sh b/scripts/prepare_release.sh index dca7095571..5fe9059474 100755 --- a/scripts/prepare_release.sh +++ b/scripts/prepare_release.sh @@ -3,6 +3,7 @@ set -eo pipefail mute=">/dev/null 2>&1" +is_subsequent_release=0 base_branch="main" build_number=0 @@ -78,24 +79,28 @@ read_command_line_arguments() { print_usage_and_exit "💥 Error: Missing argument" fi + shift 1 + + while [[ "$#" -gt 0 ]]; do + case "$1" in + -v) + mute= + ;; + -s|--subsequent) + is_subsequent_release=1 + ;; + *) + print_usage_and_exit "💥 Error: Unknown option '$1'" + ;; + esac + shift + done + if [[ $input =~ $version_regexp ]]; then process_release "$input" else process_hotfix "$input" fi - - shift 1 - - while getopts 'v' option; do - case "${option}" in - v) - mute= - ;; - *) - print_usage_and_exit "💥 Error: Unknown option '${option}'" - ;; - esac - done } process_release() { # expected input e.g. "1.72.0" @@ -104,9 +109,17 @@ process_release() { # expected input e.g. "1.72.0" echo "Processing version number: $version" - if release_branch_exists; then - is_subsequent_release=1 + if [[ "$is_subsequent_release" -eq 1 ]]; then + # check if the release branch exists (it must exist for a subsequent release) + if ! release_branch_exists; then + die "💥 Error: Release branch does not exist for a subsequent release!" + fi base_branch="$release_branch" + else + # check if the release branch does NOT exist (it must NOT exist for an initial release) + if release_branch_exists; then + die "💥 Error: Release branch already exists for an initial release!" + fi fi } @@ -242,7 +255,7 @@ main() { read_command_line_arguments "$@" checkout_base_branch - if [[ $is_subsequent_release ]]; then + if [[ "$is_subsequent_release" -eq 1 ]]; then create_build_branch elif [[ $is_hotfix ]]; then create_build_branch