From 2985eb83e722a129bd249cd66359c229e95c7546 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 9 Apr 2024 02:19:38 +0000 Subject: [PATCH 01/40] Update embedded files --- .../AppPrivacyConfigurationDataProvider.swift | 4 +- .../AppTrackerDataSetProvider.swift | 4 +- DuckDuckGo/ContentBlocker/macos-config.json | 102 +- DuckDuckGo/ContentBlocker/trackerData.json | 2172 +++++++++++------ 4 files changed, 1441 insertions(+), 841 deletions(-) diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index 07639c0bb4..f0052639b2 100644 --- a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"8838582254dfa215a99ea4d38e04cd20\"" - public static let embeddedDataSHA = "4851da5558dde2ec548d4e6ca4778e2040ad97c7edecf701de0e8ec907cb42bb" + public static let embeddedDataETag = "\"fd95ad4da437370f57ea8c2e2d03f48f\"" + public static let embeddedDataSHA = "f11d34eb516a2ba722c22e15ff8cdee5e5b2570adbf9d1b22d50438b30f57188" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift b/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift index 12d6d8fbbd..a182f36fd5 100644 --- a/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppTrackerDataSetProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppTrackerDataSetProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"07bd7f610e3fa234856abcc2b56ab10e\"" - public static let embeddedDataSHA = "1d7ef8f4c5a717a5d82f43383e33290021358d6255db12b6fdd0928e28d123ee" + public static let embeddedDataETag = "\"ef8ebcc98d8abccca793c7e04422b160\"" + public static let embeddedDataSHA = "e2e8e5e191df54227222fbb0545a7eb8634b1156a69182323981bb6aed2c639d" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index 4ddf3a01ba..216cd4c618 100644 --- a/DuckDuckGo/ContentBlocker/macos-config.json +++ b/DuckDuckGo/ContentBlocker/macos-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1712071688011, + "version": 1712611145027, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -302,11 +302,12 @@ "disabledCMPs": [ "generic-cosmetic", "termsfeed3", - "strato.de" + "strato.de", + "healthline-media" ] }, "state": "enabled", - "hash": "98e57a3eb872c9dfb4a019b90dc6c0ec" + "hash": "44af0b568856ce87b825bb7fc61b6961" }, "autofill": { "exceptions": [ @@ -1096,7 +1097,7 @@ "reason": "https://github.com/duckduckgo/privacy-configuration/issues/667" }, { - "domain": "www.canva.com", + "domain": "canva.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1818" }, { @@ -1136,12 +1137,16 @@ { "domain": "sas.dk", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1347" + }, + { + "domain": "nationalmssociety.org", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1963" } ] }, "exceptions": [], "state": "enabled", - "hash": "7edfa8344fd2577b31426130696d8b23" + "hash": "76976e1ac417949aae8cb1c7c7ca0a60" }, "dbp": { "state": "enabled", @@ -1184,7 +1189,9 @@ "videoElement": "#player video", "videoElementContainer": "#player .html5-video-player", "hoverExcluded": [], - "clickExcluded": [], + "clickExcluded": [ + "ytd-thumbnail-overlay-toggle-button-renderer" + ], "allowedEventTargets": [ ".ytp-inline-preview-scrim", ".ytd-video-preview", @@ -1231,7 +1238,7 @@ ] }, "state": "enabled", - "hash": "ab2c73639ad75b2d6efb18f324230397" + "hash": "b685397a8317384bec3b8d7e8b7571bb" }, "elementHiding": { "exceptions": [ @@ -1915,6 +1922,14 @@ { "selector": ".proper-dynamic-insertion", "type": "closest-empty" + }, + { + "selector": ".Page-header-leaderboardAd", + "type": "hide-empty" + }, + { + "selector": ".SovrnAd", + "type": "hide-empty" } ] }, @@ -2365,6 +2380,23 @@ } ] }, + { + "domain": "eurogamer.net", + "rules": [ + { + "selector": "#sticky_leaderboard", + "type": "hide-empty" + }, + { + "selector": ".primis_wrapper", + "type": "hide" + }, + { + "selector": ".autoad", + "type": "hide-empty" + } + ] + }, { "domain": "examiner.com.au", "rules": [ @@ -4222,7 +4254,7 @@ ] }, "state": "enabled", - "hash": "10545dae34a5b5f2cc26c91976be5809" + "hash": "ea31ebf0dd3e4831467ed2b2ec783279" }, "exceptionHandler": { "exceptions": [ @@ -4952,14 +4984,14 @@ "rollout": { "steps": [ { - "percent": 5 + "percent": 10 } ] } } }, "state": "enabled", - "hash": "f7cce63c16c142db4ff5764b542a6c52" + "hash": "b337f9c7cf15e7e4807ef232befaa999" }, "privacyPro": { "state": "enabled", @@ -5082,6 +5114,16 @@ "state": "disabled", "hash": "5e792dd491428702bc0104240fbce0ce" }, + "sslCertificates": { + "state": "enabled", + "exceptions": [], + "features": { + "allowBypass": { + "state": "enabled" + } + }, + "hash": "abe9584048f7f8157f71a14e7914cb1c" + }, "sync": { "state": "enabled", "features": { @@ -5366,6 +5408,7 @@ "fattoincasadabenedetta.it", "inquirer.com", "thesurfersview.com", + "twitchy.com", "wildrivers.lostcoastoutpost.com" ] }, @@ -5989,6 +6032,12 @@ "sbs.com.au" ] }, + { + "rule": "www3.doubleclick.net", + "domains": [ + "scrolller.com" + ] + }, { "rule": "doubleclick.net", "domains": [ @@ -6421,6 +6470,12 @@ "domains": [ "" ] + }, + { + "rule": "marketingplatform.google.com/about/enterprise", + "domains": [ + "scrolller.com" + ] } ] }, @@ -6429,19 +6484,7 @@ { "rule": "imasdk.googleapis.com/js/sdkloader/ima3.js", "domains": [ - "arkadium.com", - "bloomberg.com", - "cbssports.com", - "crunchyroll.com", - "gamak.tv", - "games.washingtonpost.com", - "metro.co.uk", - "nfl.com", - "pandora.com", - "paper-io.com", - "rawstory.com", - "usatoday.com", - "washingtonpost.com" + "" ] } ] @@ -6466,6 +6509,7 @@ "daotranslate.com", "drakescans.com", "duden.de", + "edealinfo.com", "freetubetv.net", "hscprojects.com", "kits4beats.com", @@ -7777,6 +7821,16 @@ } ] }, + "sundaysky.com": { + "rules": [ + { + "rule": "sundaysky.com", + "domains": [ + "bankofamerica.com" + ] + } + ] + }, "taboola.com": { "rules": [ { @@ -8209,7 +8263,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "adf596be88e8975a3cdcaaef3d24990d" + "hash": "936913b03c62ec1861b64a7a2316ddfd" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo/ContentBlocker/trackerData.json b/DuckDuckGo/ContentBlocker/trackerData.json index 647e15dbb6..f7618c909f 100644 --- a/DuckDuckGo/ContentBlocker/trackerData.json +++ b/DuckDuckGo/ContentBlocker/trackerData.json @@ -1,7 +1,7 @@ { "_builtWith": { - "tracker-radar": "09133e827d9dcbba9465c87efdf0229ddd910d3e867f8ccd5efc31abd7073963-4013b4e91930c643394cb31c6c745356f133b04f", - "tracker-surrogates": "ba0d8cefe4432723ec75b998241efd2454dff35a" + "tracker-radar": "74dd9601901673a7c0f87e609695b5a0e31b808adabd62e6db6ed7c99bde966d-4013b4e91930c643394cb31c6c745356f133b04f", + "tracker-surrogates": "0528e3226df15b1a3e319ad68ef76612a8f26623" }, "readme": "https://github.com/duckduckgo/tracker-blocklists", "trackers": { @@ -464,7 +464,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -475,7 +475,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -521,7 +521,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2409,7 +2409,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2420,7 +2420,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2464,7 +2464,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2475,7 +2475,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2590,7 +2590,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2724,7 +2724,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -2735,7 +2735,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3054,7 +3054,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3181,7 +3181,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3305,7 +3305,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3689,7 +3689,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3700,7 +3700,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3711,7 +3711,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3722,7 +3722,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3788,7 +3788,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3840,7 +3840,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3851,7 +3851,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -3988,7 +3988,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4339,7 +4339,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4374,7 +4374,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4820,7 +4820,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -4831,7 +4831,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5033,7 +5033,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5075,7 +5075,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5098,7 +5098,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5133,7 +5133,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5162,7 +5162,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5450,7 +5450,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5567,7 +5567,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5578,7 +5578,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5589,7 +5589,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5600,7 +5600,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5637,7 +5637,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -5692,7 +5692,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6228,7 +6228,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6382,7 +6382,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6698,7 +6698,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6709,7 +6709,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -6829,7 +6829,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7025,7 +7025,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7061,7 +7061,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7072,7 +7072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7083,7 +7083,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7666,7 +7666,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7677,7 +7677,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7688,7 +7688,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7769,7 +7769,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7892,7 +7892,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7903,7 +7903,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7981,7 +7981,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -7992,7 +7992,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8107,7 +8107,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8258,7 +8258,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8269,7 +8269,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8760,7 +8760,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8771,7 +8771,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -8812,7 +8812,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9228,7 +9228,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9833,7 +9833,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9844,7 +9844,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9855,7 +9855,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9895,7 +9895,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9937,7 +9937,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -9991,7 +9991,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10059,7 +10059,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10070,7 +10070,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10118,7 +10118,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10244,7 +10244,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10404,7 +10404,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10415,7 +10415,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10426,7 +10426,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10453,7 +10453,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10503,7 +10503,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -10550,7 +10550,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11028,7 +11028,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11068,7 +11068,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11804,7 +11804,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11893,7 +11893,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -11904,7 +11904,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12126,7 +12126,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12166,7 +12166,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12177,7 +12177,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12188,7 +12188,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12199,7 +12199,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12525,7 +12525,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12536,7 +12536,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -12547,7 +12547,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -14455,7 +14455,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15140,7 +15140,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15215,7 +15215,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -15266,7 +15266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16125,7 +16125,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16136,7 +16136,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16165,7 +16165,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16384,7 +16384,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16693,7 +16693,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16704,7 +16704,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16889,7 +16889,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -16938,7 +16938,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17212,7 +17212,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17223,7 +17223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -17786,7 +17786,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18134,7 +18134,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18273,7 +18273,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18632,7 +18632,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -18843,7 +18843,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20020,7 +20020,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20241,7 +20241,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20252,7 +20252,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20263,7 +20263,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20403,7 +20403,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20890,7 +20890,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20919,7 +20919,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20930,7 +20930,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -20941,7 +20941,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21005,7 +21005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21085,7 +21085,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21133,7 +21133,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21144,7 +21144,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21184,7 +21184,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21195,7 +21195,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21230,7 +21230,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21241,7 +21241,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21374,7 +21374,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21458,7 +21458,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21929,7 +21929,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21940,7 +21940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -21951,7 +21951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22021,7 +22021,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22032,7 +22032,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22185,7 +22185,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22196,7 +22196,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22238,7 +22238,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22249,7 +22249,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22260,7 +22260,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22530,7 +22530,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22541,7 +22541,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22602,7 +22602,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22660,7 +22660,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22671,7 +22671,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22771,7 +22771,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22794,7 +22794,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -22805,7 +22805,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23162,7 +23162,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23266,7 +23266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23402,7 +23402,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23477,7 +23477,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23488,7 +23488,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23532,7 +23532,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23543,7 +23543,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23554,7 +23554,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23565,7 +23565,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23645,7 +23645,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23678,7 +23678,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23729,7 +23729,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23865,7 +23865,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -23994,7 +23994,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24005,7 +24005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24252,7 +24252,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24263,7 +24263,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24274,7 +24274,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24555,7 +24555,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24590,7 +24590,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24631,7 +24631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24747,7 +24747,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24758,7 +24758,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24787,7 +24787,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24883,7 +24883,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24940,7 +24940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -24951,7 +24951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25072,7 +25072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25229,7 +25229,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25255,7 +25255,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25266,7 +25266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25329,7 +25329,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25340,7 +25340,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25351,7 +25351,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25582,7 +25582,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25609,7 +25609,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25620,7 +25620,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25631,7 +25631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25661,7 +25661,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25672,7 +25672,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25700,7 +25700,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25711,7 +25711,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25784,7 +25784,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25832,7 +25832,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25843,7 +25843,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25854,7 +25854,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25865,7 +25865,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25876,7 +25876,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25887,7 +25887,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25959,7 +25959,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -25970,7 +25970,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26024,7 +26024,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26226,7 +26226,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26534,7 +26534,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26545,7 +26545,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26556,7 +26556,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26807,7 +26807,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -26818,7 +26818,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -27236,7 +27236,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28093,7 +28093,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28223,7 +28223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28234,7 +28234,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28367,7 +28367,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28450,7 +28450,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28461,7 +28461,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -28822,7 +28822,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29129,7 +29129,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29140,7 +29140,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29266,7 +29266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -29277,7 +29277,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -31019,7 +31019,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -31324,7 +31324,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32484,7 +32484,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32495,7 +32495,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32506,7 +32506,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32517,7 +32517,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32528,7 +32528,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32539,7 +32539,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32550,7 +32550,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "accurateanimal.com": { + "domain": "accurateanimal.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32561,7 +32572,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32572,7 +32583,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32583,7 +32594,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32594,7 +32605,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32605,7 +32616,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32616,7 +32627,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32627,7 +32638,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32638,7 +32649,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32649,7 +32660,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32660,7 +32671,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32671,7 +32682,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32682,7 +32693,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32693,7 +32704,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32704,7 +32715,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32715,7 +32726,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32726,7 +32737,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32737,7 +32748,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32748,7 +32759,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32759,7 +32770,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32770,7 +32781,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32781,7 +32792,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32792,7 +32803,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32803,7 +32814,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32814,7 +32825,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32825,7 +32836,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32836,7 +32847,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32847,7 +32858,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32858,7 +32869,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32869,7 +32880,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32880,7 +32891,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32891,7 +32902,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32902,7 +32913,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32913,7 +32924,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32924,7 +32935,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32935,7 +32946,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32946,7 +32957,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32957,7 +32968,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32968,7 +32979,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32979,7 +32990,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -32990,7 +33001,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33001,7 +33012,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33012,7 +33023,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33023,7 +33034,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33034,7 +33045,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33045,7 +33056,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33056,7 +33067,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33067,7 +33078,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33078,7 +33089,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33089,7 +33100,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33100,7 +33111,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33111,7 +33122,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33122,7 +33133,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33133,7 +33144,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33144,7 +33155,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33155,7 +33166,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33166,7 +33177,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33177,7 +33188,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33188,7 +33199,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33199,7 +33210,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33210,7 +33221,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33221,7 +33232,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33232,7 +33243,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33243,7 +33254,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33254,7 +33265,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33265,7 +33276,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33276,7 +33287,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33287,7 +33298,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33298,7 +33309,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33309,7 +33320,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33320,7 +33331,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33331,7 +33342,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "calypsocapsule.com": { + "domain": "calypsocapsule.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33342,7 +33364,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33353,7 +33375,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33364,7 +33386,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33375,7 +33397,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33386,7 +33408,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33397,7 +33419,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33408,7 +33430,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33419,7 +33441,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33430,7 +33452,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33441,7 +33463,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33452,7 +33474,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33463,7 +33485,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33474,7 +33496,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33485,7 +33507,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33496,7 +33518,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "chaireggnog.com": { + "domain": "chaireggnog.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "chairsdonkey.com": { + "domain": "chairsdonkey.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33507,7 +33551,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33518,7 +33562,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33529,7 +33573,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33540,7 +33584,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33551,7 +33595,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33562,7 +33606,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "chipperisle.com": { + "domain": "chipperisle.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "chivalrouscord.com": { + "domain": "chivalrouscord.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33573,7 +33639,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33584,7 +33650,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33595,7 +33661,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33606,7 +33672,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33617,7 +33683,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "cobaltoverture.com": { + "domain": "cobaltoverture.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33628,7 +33705,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33639,7 +33716,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33650,7 +33727,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33661,7 +33738,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33672,7 +33749,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33683,7 +33760,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33694,7 +33771,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33705,7 +33782,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33716,7 +33793,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33727,7 +33804,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33738,7 +33815,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33749,7 +33826,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33760,7 +33837,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33771,7 +33848,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33782,7 +33859,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33793,7 +33870,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33804,7 +33881,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33815,7 +33892,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33826,7 +33903,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33837,7 +33914,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33848,7 +33925,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33859,7 +33936,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33870,7 +33947,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "creatorpassenger.com": { + "domain": "creatorpassenger.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33881,7 +33969,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33892,7 +33980,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33903,7 +33991,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33914,7 +34002,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33925,7 +34013,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33936,7 +34024,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33947,7 +34035,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33958,7 +34046,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33969,7 +34057,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33980,7 +34068,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -33991,7 +34079,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34002,7 +34090,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34013,7 +34101,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34024,7 +34112,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34035,7 +34123,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34046,7 +34134,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34057,7 +34145,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34068,7 +34156,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34079,7 +34167,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34090,7 +34178,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34101,7 +34189,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34112,7 +34200,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34123,7 +34211,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34134,7 +34222,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34145,7 +34233,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34156,7 +34244,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34167,7 +34255,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34178,7 +34266,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34189,7 +34277,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34200,7 +34288,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34211,7 +34299,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34222,7 +34310,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34233,7 +34321,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34244,7 +34332,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34255,7 +34343,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34266,7 +34354,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "eagerknight.com": { + "domain": "eagerknight.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "echoinghaven.com": { + "domain": "echoinghaven.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34277,7 +34387,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34288,7 +34398,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "effulgenttempest.com": { + "domain": "effulgenttempest.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34299,7 +34420,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34310,7 +34431,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34321,7 +34442,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34332,7 +34453,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34343,7 +34464,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34354,7 +34475,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34365,7 +34486,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34376,7 +34497,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "engineertrick.com": { + "domain": "engineertrick.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34387,7 +34519,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34398,7 +34530,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34409,7 +34541,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34420,7 +34552,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34431,7 +34563,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34442,7 +34574,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34453,7 +34585,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34464,7 +34596,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34475,7 +34607,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34486,7 +34618,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34497,7 +34629,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34508,7 +34640,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "exquisiteartisanship.com": { + "domain": "exquisiteartisanship.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34519,7 +34662,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34530,7 +34673,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34541,7 +34684,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34552,7 +34695,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34563,7 +34706,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34574,7 +34717,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34585,7 +34728,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34596,7 +34739,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34607,7 +34750,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34618,7 +34761,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34629,7 +34772,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34640,7 +34783,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34651,7 +34794,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34662,7 +34805,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34673,7 +34816,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34684,7 +34827,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34695,7 +34838,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "flameuncle.com": { + "domain": "flameuncle.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34706,7 +34860,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34717,7 +34871,40 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "flourishingcollaboration.com": { + "domain": "flourishingcollaboration.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "flourishinginnovation.com": { + "domain": "flourishinginnovation.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "flourishingpartnership.com": { + "domain": "flourishingpartnership.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34728,7 +34915,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34739,7 +34926,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34750,7 +34937,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34761,7 +34948,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34772,7 +34959,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34783,7 +34970,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34794,7 +34981,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34805,7 +34992,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34816,7 +35003,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34827,7 +35014,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34838,7 +35025,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34849,7 +35036,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34860,7 +35047,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34871,7 +35058,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34882,7 +35069,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34893,7 +35080,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34904,7 +35091,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34915,7 +35102,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "gladysway.com": { + "domain": "gladysway.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34926,7 +35124,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34937,7 +35135,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "glitteringbrook.com": { + "domain": "glitteringbrook.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "goldfishgrowth.com": { + "domain": "goldfishgrowth.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34948,7 +35168,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34959,7 +35179,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34970,7 +35190,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34981,7 +35201,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -34992,7 +35212,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35003,7 +35223,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35014,7 +35234,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35025,7 +35245,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35036,7 +35256,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35047,7 +35267,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35058,7 +35278,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35069,7 +35289,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35080,7 +35300,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35091,7 +35311,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35102,7 +35322,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35113,7 +35333,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35124,7 +35344,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35135,7 +35355,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35146,7 +35366,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35157,7 +35377,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35168,7 +35388,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35179,7 +35399,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35190,7 +35410,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35201,7 +35421,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35212,7 +35432,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35223,7 +35443,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35234,7 +35454,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35245,7 +35465,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35256,7 +35476,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35267,7 +35487,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35278,7 +35498,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35289,7 +35509,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35300,7 +35520,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35311,7 +35531,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35322,7 +35542,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35333,7 +35553,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35344,7 +35564,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35355,7 +35575,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35366,7 +35586,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "impulselumber.com": { + "domain": "impulselumber.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35377,7 +35608,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35388,7 +35619,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35399,7 +35630,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35410,7 +35641,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35421,7 +35652,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35432,7 +35663,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35443,7 +35674,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35454,7 +35685,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35465,7 +35696,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35476,7 +35707,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35487,7 +35718,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35498,7 +35729,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35509,7 +35740,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "keenquill.com": { + "domain": "keenquill.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35520,7 +35762,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35531,7 +35773,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35542,7 +35784,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35553,7 +35795,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35564,7 +35806,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35575,7 +35817,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "lighttalon.com": { + "domain": "lighttalon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35586,7 +35839,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35597,7 +35850,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35608,7 +35861,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35619,7 +35872,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35630,7 +35883,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35641,7 +35894,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35652,7 +35905,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35663,7 +35916,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35674,7 +35927,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35685,7 +35938,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35696,7 +35949,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35707,7 +35960,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35718,7 +35971,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35729,7 +35982,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35740,7 +35993,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "majesticwaterscape.com": { + "domain": "majesticwaterscape.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35751,7 +36015,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35762,7 +36026,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35773,7 +36037,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35784,7 +36048,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35795,7 +36059,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35806,7 +36070,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35817,7 +36081,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "melodiouscomposition.com": { + "domain": "melodiouscomposition.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35828,7 +36103,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35839,7 +36114,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35850,7 +36125,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35861,7 +36136,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35872,7 +36147,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35883,7 +36158,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35894,7 +36169,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35905,7 +36180,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35916,7 +36191,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35927,7 +36202,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35938,7 +36213,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35949,7 +36224,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35960,7 +36235,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35971,7 +36246,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35982,7 +36257,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -35993,7 +36268,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36004,7 +36279,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36015,7 +36290,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36026,7 +36301,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36037,7 +36312,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36048,7 +36323,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36059,7 +36334,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36070,7 +36345,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36081,7 +36356,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36092,7 +36367,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36103,7 +36378,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36114,7 +36389,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36125,7 +36400,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36136,7 +36411,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36147,7 +36422,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36158,7 +36433,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36169,7 +36444,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36180,7 +36455,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36191,7 +36466,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36202,7 +36477,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36213,7 +36488,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36224,7 +36499,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36235,7 +36510,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "opulentsylvan.com": { + "domain": "opulentsylvan.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36246,7 +36532,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36257,7 +36543,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36268,7 +36554,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36279,7 +36565,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36290,7 +36576,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36301,7 +36587,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36312,7 +36598,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36323,7 +36609,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36334,7 +36620,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36345,7 +36631,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36356,7 +36642,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36367,7 +36653,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36378,7 +36664,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36389,7 +36675,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36400,7 +36686,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36411,7 +36697,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36422,7 +36708,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "pluckyzone.com": { + "domain": "pluckyzone.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36433,7 +36730,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36444,7 +36741,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36455,7 +36752,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36466,7 +36763,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "polishedfolly.com": { + "domain": "polishedfolly.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36477,7 +36785,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36488,7 +36796,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "popplantation.com": { + "domain": "popplantation.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36499,7 +36818,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36510,7 +36829,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36521,7 +36840,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36532,7 +36851,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36543,7 +36862,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36554,7 +36873,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "publicsofa.com": { + "domain": "publicsofa.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36565,7 +36895,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "pulsatingmeadow.com": { + "domain": "pulsatingmeadow.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36576,7 +36917,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36587,7 +36928,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36598,7 +36939,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36609,7 +36950,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36620,7 +36961,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36631,7 +36972,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36642,7 +36983,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36653,7 +36994,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36664,7 +37005,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36675,7 +37016,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36686,7 +37027,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36697,7 +37038,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36708,7 +37049,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36719,7 +37060,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36730,7 +37071,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36741,7 +37082,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36752,7 +37093,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36763,7 +37104,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36774,7 +37115,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36785,7 +37126,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36796,7 +37137,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36807,7 +37148,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36818,7 +37159,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "relationrest.com": { + "domain": "relationrest.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "rememberdiscussion.com": { + "domain": "rememberdiscussion.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36829,7 +37192,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36840,7 +37203,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36851,7 +37214,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36862,7 +37225,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36873,7 +37236,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36884,7 +37247,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36895,7 +37258,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36906,7 +37269,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36917,7 +37280,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36928,7 +37291,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36939,7 +37302,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36950,7 +37313,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36961,7 +37324,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36972,7 +37335,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36983,7 +37346,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -36994,7 +37357,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37005,7 +37368,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37016,7 +37379,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37027,7 +37390,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37038,7 +37401,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37049,7 +37412,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37060,7 +37423,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37071,7 +37434,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37082,7 +37445,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37093,7 +37456,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37104,7 +37467,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37115,7 +37478,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37126,7 +37489,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37137,7 +37500,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37148,7 +37511,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37159,7 +37522,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37170,7 +37533,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37181,7 +37544,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37192,7 +37555,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37203,7 +37566,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "serenecascade.com": { + "domain": "serenecascade.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37214,7 +37588,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37225,7 +37599,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37236,7 +37610,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37247,7 +37621,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37258,7 +37632,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37269,7 +37643,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37280,7 +37654,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37291,7 +37665,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37302,7 +37676,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37313,7 +37687,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37324,7 +37698,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37335,7 +37709,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37346,7 +37720,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37357,7 +37731,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37368,7 +37742,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37379,7 +37753,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37390,7 +37764,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37401,7 +37775,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37412,7 +37786,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37423,7 +37797,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37434,7 +37808,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37445,7 +37819,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37456,7 +37830,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37467,7 +37841,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37478,7 +37852,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37489,7 +37863,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37500,7 +37874,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37511,7 +37885,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37522,7 +37896,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37533,7 +37907,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37544,7 +37918,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37555,7 +37929,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37566,7 +37940,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37577,7 +37951,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37588,7 +37962,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37599,7 +37973,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37610,7 +37984,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37621,7 +37995,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37632,7 +38006,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37643,7 +38017,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37654,7 +38028,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37665,7 +38039,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37676,7 +38050,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37687,7 +38061,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37698,7 +38072,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37709,7 +38083,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37720,7 +38094,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37731,7 +38105,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37742,7 +38116,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37753,7 +38127,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37764,7 +38138,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37775,7 +38149,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37786,7 +38160,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37797,7 +38171,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37808,7 +38182,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37819,7 +38193,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37830,7 +38204,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37841,7 +38215,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37852,7 +38226,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37863,7 +38237,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37874,7 +38248,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37885,7 +38259,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37896,7 +38270,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37907,7 +38281,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37918,7 +38292,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37929,7 +38303,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37940,7 +38314,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37951,7 +38325,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37962,7 +38336,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37973,7 +38347,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "sublimequartz.com": { + "domain": "sublimequartz.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37984,7 +38369,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -37995,7 +38380,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38006,7 +38391,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38017,7 +38402,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38028,7 +38413,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38039,7 +38424,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38050,7 +38435,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38061,7 +38446,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38072,7 +38457,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38083,7 +38468,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38094,7 +38479,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38105,7 +38490,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38116,7 +38501,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38127,7 +38512,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38138,7 +38523,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38149,7 +38534,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "tearfulglass.com": { + "domain": "tearfulglass.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38160,7 +38556,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38171,7 +38567,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38182,7 +38578,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38193,7 +38589,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38204,7 +38600,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38215,7 +38611,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38226,7 +38622,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38237,7 +38633,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38248,7 +38644,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38259,7 +38655,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "thrivingmarketplace.com": { + "domain": "thrivingmarketplace.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38270,7 +38677,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38281,7 +38688,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38292,7 +38699,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38303,7 +38710,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38314,7 +38721,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38325,7 +38732,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38336,7 +38743,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38347,7 +38754,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38358,7 +38765,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38369,7 +38776,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38380,7 +38787,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38391,7 +38798,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38402,7 +38809,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38413,7 +38820,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38424,7 +38831,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38435,7 +38842,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38446,7 +38853,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38457,7 +38864,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38468,7 +38875,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38479,7 +38886,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38490,7 +38897,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38501,7 +38908,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38512,7 +38919,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38523,7 +38930,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38534,7 +38941,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38545,7 +38952,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38556,7 +38963,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38567,7 +38974,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38578,7 +38985,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38589,7 +38996,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38600,7 +39007,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38611,7 +39018,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38622,7 +39029,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38633,7 +39040,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38644,7 +39051,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38655,7 +39062,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38666,7 +39073,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38677,7 +39084,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38688,7 +39095,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38699,7 +39106,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "vibrantcelebration.com": { + "domain": "vibrantcelebration.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38710,7 +39128,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38721,7 +39139,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38732,7 +39150,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38743,7 +39161,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38754,7 +39172,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38765,7 +39183,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "vividfrost.com": { + "domain": "vividfrost.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38776,7 +39205,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38787,7 +39216,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38798,7 +39227,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38809,7 +39238,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38820,7 +39249,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38831,7 +39260,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38842,7 +39271,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38853,7 +39282,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38864,7 +39293,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38875,7 +39304,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38886,7 +39315,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38897,7 +39326,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38908,7 +39337,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38919,7 +39348,18 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "wittyshack.com": { + "domain": "wittyshack.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38930,7 +39370,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38941,7 +39381,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38952,7 +39392,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38963,7 +39403,29 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "zestyhorizon.com": { + "domain": "zestyhorizon.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, + "fingerprinting": 1, + "cookies": 0.01, + "default": "block" + }, + "zestyrover.com": { + "domain": "zestyrover.com", + "owner": { + "name": "Leven Labs, Inc. DBA Admiral", + "displayName": "Admiral" + }, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38974,7 +39436,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -38985,7 +39447,7 @@ "name": "Leven Labs, Inc. DBA Admiral", "displayName": "Admiral" }, - "prevalence": 0.0151, + "prevalence": 0.0129, "fingerprinting": 1, "cookies": 0.01, "default": "block" @@ -48993,6 +49455,7 @@ "abstractedamount.com", "abstractedauthority.com", "acceptableauthority.com", + "accurateanimal.com", "accuratecoal.com", "acidpigs.com", "actoramusement.com", @@ -49088,6 +49551,7 @@ "calculatorstatement.com", "callousbrake.com", "calmcactus.com", + "calypsocapsule.com", "capablecup.com", "capriciouscorn.com", "captivatingcanyon.com", @@ -49107,6 +49571,8 @@ "ceciliavenus.com", "celestialquasar.com", "celestialspectra.com", + "chaireggnog.com", + "chairsdonkey.com", "chalkoil.com", "changeablecats.com", "chargecracker.com", @@ -49118,6 +49584,8 @@ "childlikeexample.com", "childlikeform.com", "chinsnakes.com", + "chipperisle.com", + "chivalrouscord.com", "chunkycactus.com", "circlelevel.com", "cleanhaircut.com", @@ -49125,6 +49593,7 @@ "cloisteredcurve.com", "closedcows.com", "coatfood.com", + "cobaltoverture.com", "coldbalance.com", "colossalclouds.com", "colossalcoat.com", @@ -49152,6 +49621,7 @@ "crabbychin.com", "cratecamera.com", "creatorcherry.com", + "creatorpassenger.com", "creaturecabbage.com", "crimsonmeadow.com", "critictruck.com", @@ -49204,8 +49674,11 @@ "drollwharf.com", "dustydime.com", "dustyhammer.com", + "eagerknight.com", + "echoinghaven.com", "effervescentcoral.com", "effervescentvista.com", + "effulgenttempest.com", "elasticchange.com", "elderlybean.com", "elusivebreeze.com", @@ -49215,6 +49688,7 @@ "encouragingthread.com", "endurablebulb.com", "energeticladybug.com", + "engineertrick.com", "enigmaticcanyon.com", "enigmaticvoyage.com", "enormousearth.com", @@ -49230,6 +49704,7 @@ "executeknowledge.com", "exhibitsneeze.com", "expansioneggnog.com", + "exquisiteartisanship.com", "exuberantedge.com", "fadedsnow.com", "fadewaves.com", @@ -49253,8 +49728,12 @@ "financefear.com", "firstfrogs.com", "fixedfold.com", + "flameuncle.com", "flimsycircle.com", "flimsythought.com", + "flourishingcollaboration.com", + "flourishinginnovation.com", + "flourishingpartnership.com", "flowerstreatment.com", "flowerycreature.com", "floweryfact.com", @@ -49284,9 +49763,12 @@ "giddycoat.com", "giraffepiano.com", "givevacation.com", + "gladysway.com", "gleamingcow.com", "glisteningguide.com", + "glitteringbrook.com", "gloriousbeef.com", + "goldfishgrowth.com", "gondolagnome.com", "gorgeousedge.com", "gracefulmilk.com", @@ -49337,6 +49819,7 @@ "importantmeat.com", "impossibleexpansion.com", "impulsejewel.com", + "impulselumber.com", "incompetentjoke.com", "inconclusiveaction.com", "inputicicle.com", @@ -49351,6 +49834,7 @@ "jubilanttempest.com", "jubilantwhisper.com", "kaputquill.com", + "keenquill.com", "knitstamp.com", "knottyswing.com", "laboredlocket.com", @@ -49360,6 +49844,7 @@ "leftliquid.com", "liftedknowledge.com", "lightenafterthought.com", + "lighttalon.com", "livelumber.com", "livelylaugh.com", "livelyreward.com", @@ -49379,6 +49864,7 @@ "lunchroomlock.com", "lustroushaven.com", "maddeningpowder.com", + "majesticwaterscape.com", "maliciousmusic.com", "marketspiders.com", "marriedbelief.com", @@ -49390,6 +49876,7 @@ "meatydime.com", "meddleplant.com", "melodiouschorus.com", + "melodiouscomposition.com", "meltmilk.com", "memopilot.com", "memorizematch.com", @@ -49435,6 +49922,7 @@ "oldfashionedoffer.com", "operationchicken.com", "optimallimit.com", + "opulentsylvan.com", "orientedargument.com", "outstandingincome.com", "outstandingsnails.com", @@ -49462,13 +49950,16 @@ "pleasantpump.com", "plotrabbit.com", "pluckypocket.com", + "pluckyzone.com", "pocketfaucet.com", "poeticpackage.com", "pointdigestion.com", "pointlesspocket.com", "pointlessprofit.com", + "polishedfolly.com", "politeplanes.com", "politicalporter.com", + "popplantation.com", "possibleboats.com", "possiblepencil.com", "potatoinvention.com", @@ -49484,7 +49975,9 @@ "profusesupport.com", "protestcopy.com", "psychedelicarithmetic.com", + "publicsofa.com", "puffypurpose.com", + "pulsatingmeadow.com", "pumpedpancake.com", "punyplant.com", "purposepipe.com", @@ -49519,6 +50012,8 @@ "regularplants.com", "regulatesleet.com", "rehabilitatereason.com", + "relationrest.com", + "rememberdiscussion.com", "repeatsweater.com", "replaceroute.com", "resonantbrush.com", @@ -49576,6 +50071,7 @@ "selfishsnake.com", "separatesort.com", "seraphicjubilee.com", + "serenecascade.com", "serenepebble.com", "serioussuit.com", "serpentshampoo.com", @@ -49679,6 +50175,7 @@ "stupendoussleet.com", "stupendoussnow.com", "stupidscene.com", + "sublimequartz.com", "succeedscene.com", "sugarfriction.com", "suggestionbridge.com", @@ -49700,6 +50197,7 @@ "tangycover.com", "tastelesstrees.com", "tastelesstrucks.com", + "tearfulglass.com", "tediousticket.com", "teenytinycellar.com", "teenytinyshirt.com", @@ -49715,6 +50213,7 @@ "thomastorch.com", "thoughtlessknot.com", "threetruck.com", + "thrivingmarketplace.com", "ticketaunt.com", "tidymitten.com", "tiredthroat.com", @@ -49763,12 +50262,14 @@ "verdantlabyrinth.com", "verdantloom.com", "verseballs.com", + "vibrantcelebration.com", "vibrantgale.com", "vibranthaven.com", "vibrantpact.com", "vibranttalisman.com", "virtualvincent.com", "vividcanopy.com", + "vividfrost.com", "vividmeadow.com", "vividplume.com", "volatileprofit.com", @@ -49787,15 +50288,18 @@ "whispermeeting.com", "wildcommittee.com", "wistfulwaste.com", + "wittyshack.com", "workoperation.com", "wretchedfloor.com", "wrongwound.com", "zephyrlabyrinth.com", "zestycrime.com", + "zestyhorizon.com", + "zestyrover.com", "zipperxray.com", "zlp6s.pw" ], - "prevalence": 0.0151, + "prevalence": 0.0129, "displayName": "Admiral" } }, @@ -50523,6 +51027,7 @@ "abstractedamount.com": "Leven Labs, Inc. DBA Admiral", "abstractedauthority.com": "Leven Labs, Inc. DBA Admiral", "acceptableauthority.com": "Leven Labs, Inc. DBA Admiral", + "accurateanimal.com": "Leven Labs, Inc. DBA Admiral", "accuratecoal.com": "Leven Labs, Inc. DBA Admiral", "acidpigs.com": "Leven Labs, Inc. DBA Admiral", "actoramusement.com": "Leven Labs, Inc. DBA Admiral", @@ -50618,6 +51123,7 @@ "calculatorstatement.com": "Leven Labs, Inc. DBA Admiral", "callousbrake.com": "Leven Labs, Inc. DBA Admiral", "calmcactus.com": "Leven Labs, Inc. DBA Admiral", + "calypsocapsule.com": "Leven Labs, Inc. DBA Admiral", "capablecup.com": "Leven Labs, Inc. DBA Admiral", "capriciouscorn.com": "Leven Labs, Inc. DBA Admiral", "captivatingcanyon.com": "Leven Labs, Inc. DBA Admiral", @@ -50637,6 +51143,8 @@ "ceciliavenus.com": "Leven Labs, Inc. DBA Admiral", "celestialquasar.com": "Leven Labs, Inc. DBA Admiral", "celestialspectra.com": "Leven Labs, Inc. DBA Admiral", + "chaireggnog.com": "Leven Labs, Inc. DBA Admiral", + "chairsdonkey.com": "Leven Labs, Inc. DBA Admiral", "chalkoil.com": "Leven Labs, Inc. DBA Admiral", "changeablecats.com": "Leven Labs, Inc. DBA Admiral", "chargecracker.com": "Leven Labs, Inc. DBA Admiral", @@ -50648,6 +51156,8 @@ "childlikeexample.com": "Leven Labs, Inc. DBA Admiral", "childlikeform.com": "Leven Labs, Inc. DBA Admiral", "chinsnakes.com": "Leven Labs, Inc. DBA Admiral", + "chipperisle.com": "Leven Labs, Inc. DBA Admiral", + "chivalrouscord.com": "Leven Labs, Inc. DBA Admiral", "chunkycactus.com": "Leven Labs, Inc. DBA Admiral", "circlelevel.com": "Leven Labs, Inc. DBA Admiral", "cleanhaircut.com": "Leven Labs, Inc. DBA Admiral", @@ -50655,6 +51165,7 @@ "cloisteredcurve.com": "Leven Labs, Inc. DBA Admiral", "closedcows.com": "Leven Labs, Inc. DBA Admiral", "coatfood.com": "Leven Labs, Inc. DBA Admiral", + "cobaltoverture.com": "Leven Labs, Inc. DBA Admiral", "coldbalance.com": "Leven Labs, Inc. DBA Admiral", "colossalclouds.com": "Leven Labs, Inc. DBA Admiral", "colossalcoat.com": "Leven Labs, Inc. DBA Admiral", @@ -50682,6 +51193,7 @@ "crabbychin.com": "Leven Labs, Inc. DBA Admiral", "cratecamera.com": "Leven Labs, Inc. DBA Admiral", "creatorcherry.com": "Leven Labs, Inc. DBA Admiral", + "creatorpassenger.com": "Leven Labs, Inc. DBA Admiral", "creaturecabbage.com": "Leven Labs, Inc. DBA Admiral", "crimsonmeadow.com": "Leven Labs, Inc. DBA Admiral", "critictruck.com": "Leven Labs, Inc. DBA Admiral", @@ -50734,8 +51246,11 @@ "drollwharf.com": "Leven Labs, Inc. DBA Admiral", "dustydime.com": "Leven Labs, Inc. DBA Admiral", "dustyhammer.com": "Leven Labs, Inc. DBA Admiral", + "eagerknight.com": "Leven Labs, Inc. DBA Admiral", + "echoinghaven.com": "Leven Labs, Inc. DBA Admiral", "effervescentcoral.com": "Leven Labs, Inc. DBA Admiral", "effervescentvista.com": "Leven Labs, Inc. DBA Admiral", + "effulgenttempest.com": "Leven Labs, Inc. DBA Admiral", "elasticchange.com": "Leven Labs, Inc. DBA Admiral", "elderlybean.com": "Leven Labs, Inc. DBA Admiral", "elusivebreeze.com": "Leven Labs, Inc. DBA Admiral", @@ -50745,6 +51260,7 @@ "encouragingthread.com": "Leven Labs, Inc. DBA Admiral", "endurablebulb.com": "Leven Labs, Inc. DBA Admiral", "energeticladybug.com": "Leven Labs, Inc. DBA Admiral", + "engineertrick.com": "Leven Labs, Inc. DBA Admiral", "enigmaticcanyon.com": "Leven Labs, Inc. DBA Admiral", "enigmaticvoyage.com": "Leven Labs, Inc. DBA Admiral", "enormousearth.com": "Leven Labs, Inc. DBA Admiral", @@ -50760,6 +51276,7 @@ "executeknowledge.com": "Leven Labs, Inc. DBA Admiral", "exhibitsneeze.com": "Leven Labs, Inc. DBA Admiral", "expansioneggnog.com": "Leven Labs, Inc. DBA Admiral", + "exquisiteartisanship.com": "Leven Labs, Inc. DBA Admiral", "exuberantedge.com": "Leven Labs, Inc. DBA Admiral", "fadedsnow.com": "Leven Labs, Inc. DBA Admiral", "fadewaves.com": "Leven Labs, Inc. DBA Admiral", @@ -50783,8 +51300,12 @@ "financefear.com": "Leven Labs, Inc. DBA Admiral", "firstfrogs.com": "Leven Labs, Inc. DBA Admiral", "fixedfold.com": "Leven Labs, Inc. DBA Admiral", + "flameuncle.com": "Leven Labs, Inc. DBA Admiral", "flimsycircle.com": "Leven Labs, Inc. DBA Admiral", "flimsythought.com": "Leven Labs, Inc. DBA Admiral", + "flourishingcollaboration.com": "Leven Labs, Inc. DBA Admiral", + "flourishinginnovation.com": "Leven Labs, Inc. DBA Admiral", + "flourishingpartnership.com": "Leven Labs, Inc. DBA Admiral", "flowerstreatment.com": "Leven Labs, Inc. DBA Admiral", "flowerycreature.com": "Leven Labs, Inc. DBA Admiral", "floweryfact.com": "Leven Labs, Inc. DBA Admiral", @@ -50814,9 +51335,12 @@ "giddycoat.com": "Leven Labs, Inc. DBA Admiral", "giraffepiano.com": "Leven Labs, Inc. DBA Admiral", "givevacation.com": "Leven Labs, Inc. DBA Admiral", + "gladysway.com": "Leven Labs, Inc. DBA Admiral", "gleamingcow.com": "Leven Labs, Inc. DBA Admiral", "glisteningguide.com": "Leven Labs, Inc. DBA Admiral", + "glitteringbrook.com": "Leven Labs, Inc. DBA Admiral", "gloriousbeef.com": "Leven Labs, Inc. DBA Admiral", + "goldfishgrowth.com": "Leven Labs, Inc. DBA Admiral", "gondolagnome.com": "Leven Labs, Inc. DBA Admiral", "gorgeousedge.com": "Leven Labs, Inc. DBA Admiral", "gracefulmilk.com": "Leven Labs, Inc. DBA Admiral", @@ -50867,6 +51391,7 @@ "importantmeat.com": "Leven Labs, Inc. DBA Admiral", "impossibleexpansion.com": "Leven Labs, Inc. DBA Admiral", "impulsejewel.com": "Leven Labs, Inc. DBA Admiral", + "impulselumber.com": "Leven Labs, Inc. DBA Admiral", "incompetentjoke.com": "Leven Labs, Inc. DBA Admiral", "inconclusiveaction.com": "Leven Labs, Inc. DBA Admiral", "inputicicle.com": "Leven Labs, Inc. DBA Admiral", @@ -50881,6 +51406,7 @@ "jubilanttempest.com": "Leven Labs, Inc. DBA Admiral", "jubilantwhisper.com": "Leven Labs, Inc. DBA Admiral", "kaputquill.com": "Leven Labs, Inc. DBA Admiral", + "keenquill.com": "Leven Labs, Inc. DBA Admiral", "knitstamp.com": "Leven Labs, Inc. DBA Admiral", "knottyswing.com": "Leven Labs, Inc. DBA Admiral", "laboredlocket.com": "Leven Labs, Inc. DBA Admiral", @@ -50890,6 +51416,7 @@ "leftliquid.com": "Leven Labs, Inc. DBA Admiral", "liftedknowledge.com": "Leven Labs, Inc. DBA Admiral", "lightenafterthought.com": "Leven Labs, Inc. DBA Admiral", + "lighttalon.com": "Leven Labs, Inc. DBA Admiral", "livelumber.com": "Leven Labs, Inc. DBA Admiral", "livelylaugh.com": "Leven Labs, Inc. DBA Admiral", "livelyreward.com": "Leven Labs, Inc. DBA Admiral", @@ -50909,6 +51436,7 @@ "lunchroomlock.com": "Leven Labs, Inc. DBA Admiral", "lustroushaven.com": "Leven Labs, Inc. DBA Admiral", "maddeningpowder.com": "Leven Labs, Inc. DBA Admiral", + "majesticwaterscape.com": "Leven Labs, Inc. DBA Admiral", "maliciousmusic.com": "Leven Labs, Inc. DBA Admiral", "marketspiders.com": "Leven Labs, Inc. DBA Admiral", "marriedbelief.com": "Leven Labs, Inc. DBA Admiral", @@ -50920,6 +51448,7 @@ "meatydime.com": "Leven Labs, Inc. DBA Admiral", "meddleplant.com": "Leven Labs, Inc. DBA Admiral", "melodiouschorus.com": "Leven Labs, Inc. DBA Admiral", + "melodiouscomposition.com": "Leven Labs, Inc. DBA Admiral", "meltmilk.com": "Leven Labs, Inc. DBA Admiral", "memopilot.com": "Leven Labs, Inc. DBA Admiral", "memorizematch.com": "Leven Labs, Inc. DBA Admiral", @@ -50965,6 +51494,7 @@ "oldfashionedoffer.com": "Leven Labs, Inc. DBA Admiral", "operationchicken.com": "Leven Labs, Inc. DBA Admiral", "optimallimit.com": "Leven Labs, Inc. DBA Admiral", + "opulentsylvan.com": "Leven Labs, Inc. DBA Admiral", "orientedargument.com": "Leven Labs, Inc. DBA Admiral", "outstandingincome.com": "Leven Labs, Inc. DBA Admiral", "outstandingsnails.com": "Leven Labs, Inc. DBA Admiral", @@ -50992,13 +51522,16 @@ "pleasantpump.com": "Leven Labs, Inc. DBA Admiral", "plotrabbit.com": "Leven Labs, Inc. DBA Admiral", "pluckypocket.com": "Leven Labs, Inc. DBA Admiral", + "pluckyzone.com": "Leven Labs, Inc. DBA Admiral", "pocketfaucet.com": "Leven Labs, Inc. DBA Admiral", "poeticpackage.com": "Leven Labs, Inc. DBA Admiral", "pointdigestion.com": "Leven Labs, Inc. DBA Admiral", "pointlesspocket.com": "Leven Labs, Inc. DBA Admiral", "pointlessprofit.com": "Leven Labs, Inc. DBA Admiral", + "polishedfolly.com": "Leven Labs, Inc. DBA Admiral", "politeplanes.com": "Leven Labs, Inc. DBA Admiral", "politicalporter.com": "Leven Labs, Inc. DBA Admiral", + "popplantation.com": "Leven Labs, Inc. DBA Admiral", "possibleboats.com": "Leven Labs, Inc. DBA Admiral", "possiblepencil.com": "Leven Labs, Inc. DBA Admiral", "potatoinvention.com": "Leven Labs, Inc. DBA Admiral", @@ -51014,7 +51547,9 @@ "profusesupport.com": "Leven Labs, Inc. DBA Admiral", "protestcopy.com": "Leven Labs, Inc. DBA Admiral", "psychedelicarithmetic.com": "Leven Labs, Inc. DBA Admiral", + "publicsofa.com": "Leven Labs, Inc. DBA Admiral", "puffypurpose.com": "Leven Labs, Inc. DBA Admiral", + "pulsatingmeadow.com": "Leven Labs, Inc. DBA Admiral", "pumpedpancake.com": "Leven Labs, Inc. DBA Admiral", "punyplant.com": "Leven Labs, Inc. DBA Admiral", "purposepipe.com": "Leven Labs, Inc. DBA Admiral", @@ -51049,6 +51584,8 @@ "regularplants.com": "Leven Labs, Inc. DBA Admiral", "regulatesleet.com": "Leven Labs, Inc. DBA Admiral", "rehabilitatereason.com": "Leven Labs, Inc. DBA Admiral", + "relationrest.com": "Leven Labs, Inc. DBA Admiral", + "rememberdiscussion.com": "Leven Labs, Inc. DBA Admiral", "repeatsweater.com": "Leven Labs, Inc. DBA Admiral", "replaceroute.com": "Leven Labs, Inc. DBA Admiral", "resonantbrush.com": "Leven Labs, Inc. DBA Admiral", @@ -51106,6 +51643,7 @@ "selfishsnake.com": "Leven Labs, Inc. DBA Admiral", "separatesort.com": "Leven Labs, Inc. DBA Admiral", "seraphicjubilee.com": "Leven Labs, Inc. DBA Admiral", + "serenecascade.com": "Leven Labs, Inc. DBA Admiral", "serenepebble.com": "Leven Labs, Inc. DBA Admiral", "serioussuit.com": "Leven Labs, Inc. DBA Admiral", "serpentshampoo.com": "Leven Labs, Inc. DBA Admiral", @@ -51209,6 +51747,7 @@ "stupendoussleet.com": "Leven Labs, Inc. DBA Admiral", "stupendoussnow.com": "Leven Labs, Inc. DBA Admiral", "stupidscene.com": "Leven Labs, Inc. DBA Admiral", + "sublimequartz.com": "Leven Labs, Inc. DBA Admiral", "succeedscene.com": "Leven Labs, Inc. DBA Admiral", "sugarfriction.com": "Leven Labs, Inc. DBA Admiral", "suggestionbridge.com": "Leven Labs, Inc. DBA Admiral", @@ -51230,6 +51769,7 @@ "tangycover.com": "Leven Labs, Inc. DBA Admiral", "tastelesstrees.com": "Leven Labs, Inc. DBA Admiral", "tastelesstrucks.com": "Leven Labs, Inc. DBA Admiral", + "tearfulglass.com": "Leven Labs, Inc. DBA Admiral", "tediousticket.com": "Leven Labs, Inc. DBA Admiral", "teenytinycellar.com": "Leven Labs, Inc. DBA Admiral", "teenytinyshirt.com": "Leven Labs, Inc. DBA Admiral", @@ -51245,6 +51785,7 @@ "thomastorch.com": "Leven Labs, Inc. DBA Admiral", "thoughtlessknot.com": "Leven Labs, Inc. DBA Admiral", "threetruck.com": "Leven Labs, Inc. DBA Admiral", + "thrivingmarketplace.com": "Leven Labs, Inc. DBA Admiral", "ticketaunt.com": "Leven Labs, Inc. DBA Admiral", "tidymitten.com": "Leven Labs, Inc. DBA Admiral", "tiredthroat.com": "Leven Labs, Inc. DBA Admiral", @@ -51293,12 +51834,14 @@ "verdantlabyrinth.com": "Leven Labs, Inc. DBA Admiral", "verdantloom.com": "Leven Labs, Inc. DBA Admiral", "verseballs.com": "Leven Labs, Inc. DBA Admiral", + "vibrantcelebration.com": "Leven Labs, Inc. DBA Admiral", "vibrantgale.com": "Leven Labs, Inc. DBA Admiral", "vibranthaven.com": "Leven Labs, Inc. DBA Admiral", "vibrantpact.com": "Leven Labs, Inc. DBA Admiral", "vibranttalisman.com": "Leven Labs, Inc. DBA Admiral", "virtualvincent.com": "Leven Labs, Inc. DBA Admiral", "vividcanopy.com": "Leven Labs, Inc. DBA Admiral", + "vividfrost.com": "Leven Labs, Inc. DBA Admiral", "vividmeadow.com": "Leven Labs, Inc. DBA Admiral", "vividplume.com": "Leven Labs, Inc. DBA Admiral", "volatileprofit.com": "Leven Labs, Inc. DBA Admiral", @@ -51317,11 +51860,14 @@ "whispermeeting.com": "Leven Labs, Inc. DBA Admiral", "wildcommittee.com": "Leven Labs, Inc. DBA Admiral", "wistfulwaste.com": "Leven Labs, Inc. DBA Admiral", + "wittyshack.com": "Leven Labs, Inc. DBA Admiral", "workoperation.com": "Leven Labs, Inc. DBA Admiral", "wretchedfloor.com": "Leven Labs, Inc. DBA Admiral", "wrongwound.com": "Leven Labs, Inc. DBA Admiral", "zephyrlabyrinth.com": "Leven Labs, Inc. DBA Admiral", "zestycrime.com": "Leven Labs, Inc. DBA Admiral", + "zestyhorizon.com": "Leven Labs, Inc. DBA Admiral", + "zestyrover.com": "Leven Labs, Inc. DBA Admiral", "zipperxray.com": "Leven Labs, Inc. DBA Admiral", "zlp6s.pw": "Leven Labs, Inc. DBA Admiral", "abtasty.com": "AB Tasty", From e05ea229781c7b459116656d5a919e08c4f25375 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 9 Apr 2024 02:19:38 +0000 Subject: [PATCH 02/40] Set marketing version to 1.83.0 --- Configuration/Version.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 4aebe00d0f..71f4ed0cad 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 1.82.0 +MARKETING_VERSION = 1.83.0 From 0c0e490a1171578204b9068e83ae93e880e03eaa Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 9 Apr 2024 02:29:43 +0000 Subject: [PATCH 03/40] Bump version to 1.83.0 (153) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 92feb2ccd4..06e5c06c5d 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 152 +CURRENT_PROJECT_VERSION = 153 From 101f9408d351a82ed1e86d2ad3b5731bcd0e698b Mon Sep 17 00:00:00 2001 From: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Date: Tue, 9 Apr 2024 04:43:49 +0200 Subject: [PATCH 04/40] Update autoconsent to v10.5.0 (#2548) Task/Issue URL: https://app.asana.com/0/1207001382250025/1207001382250025 Autoconsent Release: https://github.com/duckduckgo/autoconsent/releases/tag/v10.5.0 Description Updates Autoconsent to version v10.5.0. --- DuckDuckGo/Autoconsent/autoconsent-bundle.js | 2 +- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js index db02dcc60b..d7ccb86ad9 100644 --- a/DuckDuckGo/Autoconsent/autoconsent-bundle.js +++ b/DuckDuckGo/Autoconsent/autoconsent-bundle.js @@ -1 +1 @@ -!function(){"use strict";var e=class e{static setBase(t){e.base=t}static findElement(t,o=null,c=!1){let i=null;return i=null!=o?Array.from(o.querySelectorAll(t.selector)):null!=e.base?Array.from(e.base.querySelectorAll(t.selector)):Array.from(document.querySelectorAll(t.selector)),null!=t.textFilter&&(i=i.filter((e=>{const o=e.textContent.toLowerCase();if(Array.isArray(t.textFilter)){let e=!1;for(const c of t.textFilter)if(-1!==o.indexOf(c.toLowerCase())){e=!0;break}return e}if(null!=t.textFilter)return-1!==o.indexOf(t.textFilter.toLowerCase())}))),null!=t.styleFilters&&(i=i.filter((e=>{const o=window.getComputedStyle(e);let c=!0;for(const e of t.styleFilters){const t=o[e.option];c=e.negated?c&&t!==e.value:c&&t===e.value}return c}))),null!=t.displayFilter&&(i=i.filter((e=>t.displayFilter?0!==e.offsetHeight:0===e.offsetHeight))),null!=t.iframeFilter&&(i=i.filter((()=>t.iframeFilter?window.location!==window.parent.location:window.location===window.parent.location))),null!=t.childFilter&&(i=i.filter((o=>{const c=e.base;e.setBase(o);const i=e.find(t.childFilter);return e.setBase(c),null!=i.target}))),c?i:(i.length>1&&console.warn("Multiple possible targets: ",i,t,o),i[0])}static find(t,o=!1){const c=[];if(null!=t.parent){const i=e.findElement(t.parent,null,o);if(null!=i){if(i instanceof Array)return i.forEach((i=>{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})})),c;{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})}}}else{const i=e.findElement(t.target,null,o);i instanceof Array?i.forEach((e=>{c.push({parent:null,target:e})})):c.push({parent:null,target:i})}return 0===c.length&&c.push({parent:null,target:null}),o?c:(1!==c.length&&console.warn("Multiple results found, even though multiple false",c),c[0])}};e.base=null;var t=e;function o(e){const o=t.find(e);return"css"===e.type?!!o.target:"checkbox"===e.type?!!o.target&&o.target.checked:void 0}async function c(e,n){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return i(0)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,n);case"consent":return async function(e,t){for(const i of e.consents){const e=-1!==t.indexOf(i.type);if(i.matcher&&i.toggleAction){o(i.matcher)!==e&&await c(i.toggleAction)}else e?await c(i.trueAction):await c(i.falseAction)}}(e,n);case"ifcss":return async function(e,o){const i=t.find(e);i.target?e.falseAction&&await c(e.falseAction,o):e.trueAction&&await c(e.trueAction,o)}(e,n);case"waitcss":return async function(e){await new Promise((o=>{let c=e.retries||10;const i=e.waitTime||250,n=()=>{const a=t.find(e);(e.negated&&a.target||!e.negated&&!a.target)&&c>0?(c-=1,setTimeout(n,i)):o()};n()}))}(e);case"foreach":return async function(e,o){const i=t.find(e,!0),n=t.base;for(const n of i)n.target&&(t.setBase(n.target),await c(e.action,o));t.setBase(n)}(e,n);case"hide":return async function(e){const o=t.find(e);o.target&&o.target.classList.add("Autoconsent-Hidden")}(e);case"slide":return async function(e){const o=t.find(e),c=t.find(e.dragTarget);if(o.target){const e=o.target.getBoundingClientRect(),t=c.target.getBoundingClientRect();let i=t.top-e.top,n=t.left-e.left;"y"===this.config.axis.toLowerCase()&&(n=0),"x"===this.config.axis.toLowerCase()&&(i=0);const a=window.screenX+e.left+e.width/2,s=window.screenY+e.top+e.height/2,r=e.left+e.width/2,l=e.top+e.height/2,p=document.createEvent("MouseEvents");p.initMouseEvent("mousedown",!0,!0,window,0,a,s,r,l,!1,!1,!1,!1,0,o.target);const d=document.createEvent("MouseEvents");d.initMouseEvent("mousemove",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target);const u=document.createEvent("MouseEvents");u.initMouseEvent("mouseup",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target),o.target.dispatchEvent(p),await this.waitTimeout(10),o.target.dispatchEvent(d),await this.waitTimeout(10),o.target.dispatchEvent(u)}}(e);case"close":return async function(){window.close()}();case"wait":return async function(e){await i(e.waitTime)}(e);case"eval":return async function(e){return console.log("eval!",e.code),new Promise((t=>{try{e.async?(window.eval(e.code),setTimeout((()=>{t(window.eval("window.__consentCheckResult"))}),e.timeout||250)):t(window.eval(e.code))}catch(o){console.warn("eval error",o,e.code),t(!1)}}))}(e);default:throw"Unknown action type: "+e.type}}function i(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function n(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var a={pending:new Map,sendContentMessage:null};function s(e,t){const o=n();a.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new class{constructor(e,t=1e3){this.id=e,this.promise=new Promise(((e,t)=>{this.resolve=e,this.reject=t})),this.timer=window.setTimeout((()=>{this.reject(new Error("timeout"))}),t)}}(o);return a.pending.set(c.id,c),c.promise}var r={EVAL_0:()=>console.log(1),EVAL_CONSENTMANAGER_1:()=>window.__cmp&&"object"==typeof __cmp("getCMPData"),EVAL_CONSENTMANAGER_2:()=>!__cmp("consentStatus").userChoiceExists,EVAL_CONSENTMANAGER_3:()=>__cmp("setConsent",0),EVAL_CONSENTMANAGER_4:()=>__cmp("setConsent",1),EVAL_CONSENTMANAGER_5:()=>__cmp("consentStatus").userChoiceExists,EVAL_COOKIEBOT_1:()=>!!window.Cookiebot,EVAL_COOKIEBOT_2:()=>!window.Cookiebot.hasResponse&&!0===window.Cookiebot.dialog?.visible,EVAL_COOKIEBOT_3:()=>window.Cookiebot.withdraw()||!0,EVAL_COOKIEBOT_4:()=>window.Cookiebot.hide()||!0,EVAL_COOKIEBOT_5:()=>!0===window.Cookiebot.declined,EVAL_KLARO_1:()=>{const e=globalThis.klaroConfig||globalThis.klaro?.getManager&&globalThis.klaro.getManager().config;if(!e)return!0;const t=(e.services||e.apps).filter((e=>!e.required)).map((e=>e.name));if(klaro&&klaro.getManager){const e=klaro.getManager();return t.every((t=>!e.consents[t]))}if(klaroConfig&&"cookie"===klaroConfig.storageMethod){const e=klaroConfig.cookieName||klaroConfig.storageName,o=JSON.parse(decodeURIComponent(document.cookie.split(";").find((t=>t.trim().startsWith(e))).split("=")[1]));return Object.keys(o).filter((e=>t.includes(e))).every((e=>!1===o[e]))}},EVAL_ONETRUST_1:()=>window.OnetrustActiveGroups.split(",").filter((e=>e.length>0)).length<=1,EVAL_TRUSTARC_TOP:()=>window&&window.truste&&"0"===window.truste.eu.bindMap.prefCookie,EVAL_ADROLL_0:()=>!document.cookie.includes("__adroll_fpc"),EVAL_ALMACMP_0:()=>document.cookie.includes('"name":"Google","consent":false'),EVAL_AFFINITY_SERIF_COM_0:()=>document.cookie.includes("serif_manage_cookies_viewed")&&!document.cookie.includes("serif_allow_analytics"),EVAL_ARBEITSAGENTUR_TEST:()=>document.cookie.includes("cookie_consent=denied"),EVAL_AXEPTIO_0:()=>document.cookie.includes("axeptio_authorized_vendors=%2C%2C"),EVAL_BAHN_TEST:()=>1===utag.gdpr.getSelectedCategories().length,EVAL_BING_0:()=>document.cookie.includes("AL=0")&&document.cookie.includes("AD=0")&&document.cookie.includes("SM=0"),EVAL_BLOCKSY_0:()=>document.cookie.includes("blocksy_cookies_consent_accepted=no"),EVAL_BORLABS_0:()=>!JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("borlabs-cookie"))).split("=",2)[1])).consents.statistics,EVAL_BUNDESREGIERUNG_DE_0:()=>document.cookie.match("cookie-allow-tracking=0"),EVAL_CANVA_0:()=>!document.cookie.includes("gtm_fpc_engagement_event"),EVAL_CC_BANNER2_0:()=>!!document.cookie.match(/sncc=[^;]+D%3Dtrue/),EVAL_CLICKIO_0:()=>document.cookie.includes("__lxG__consent__v2_daisybit="),EVAL_CLINCH_0:()=>document.cookie.includes("ctc_rejected=1"),EVAL_COOKIECONSENT2_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COOKIECONSENT3_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COINBASE_0:()=>JSON.parse(decodeURIComponent(document.cookie.match(/cm_(eu|default)_preferences=([0-9a-zA-Z\\{\\}\\[\\]%:]*);?/)[2])).consent.length<=1,EVAL_COMPLIANZ_BANNER_0:()=>document.cookie.includes("cmplz_banner-status=dismissed"),EVAL_COOKIE_LAW_INFO_0:()=>CLI.disableAllCookies()||CLI.reject_close()||!0,EVAL_COOKIE_LAW_INFO_1:()=>-1===document.cookie.indexOf("cookielawinfo-checkbox-non-necessary=yes"),EVAL_COOKIE_LAW_INFO_DETECT:()=>!!window.CLI,EVAL_COOKIE_MANAGER_POPUP_0:()=>!1===JSON.parse(document.cookie.split(";").find((e=>e.trim().startsWith("CookieLevel"))).split("=")[1]).social,EVAL_COOKIEALERT_0:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_1:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_2:()=>!0===window.CookieConsent.declined,EVAL_COOKIEFIRST_0:()=>{return!1===(e=JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("cookiefirst"))).trim()).split("=")[1])).performance&&!1===e.functional&&!1===e.advertising;var e},EVAL_COOKIEFIRST_1:()=>document.querySelectorAll("button[data-cookiefirst-accent-color=true][role=checkbox]:not([disabled])").forEach((e=>"true"==e.getAttribute("aria-checked")&&e.click()))||!0,EVAL_COOKIEINFORMATION_0:()=>CookieInformation.declineAllCategories()||!0,EVAL_COOKIEINFORMATION_1:()=>CookieInformation.submitAllCategories()||!0,EVAL_COOKIEINFORMATION_2:()=>document.cookie.includes("CookieInformationConsent="),EVAL_COOKIEYES_0:()=>document.cookie.includes("advertisement:no"),EVAL_DAILYMOTION_0:()=>!!document.cookie.match("dm-euconsent-v2"),EVAL_DNDBEYOND_TEST:()=>document.cookie.includes("cookie-consent=denied"),EVAL_DSGVO_0:()=>!document.cookie.includes("sp_dsgvo_cookie_settings"),EVAL_DUNELM_0:()=>document.cookie.includes("cc_functional=0")&&document.cookie.includes("cc_targeting=0"),EVAL_ETSY_0:()=>document.querySelectorAll(".gdpr-overlay-body input").forEach((e=>{e.checked=!1}))||!0,EVAL_ETSY_1:()=>document.querySelector(".gdpr-overlay-view button[data-wt-overlay-close]").click()||!0,EVAL_EU_COOKIE_COMPLIANCE_0:()=>-1===document.cookie.indexOf("cookie-agreed=2"),EVAL_EU_COOKIE_LAW_0:()=>!document.cookie.includes("euCookie"),EVAL_EZOIC_0:()=>ezCMP.handleAcceptAllClick(),EVAL_EZOIC_1:()=>!!document.cookie.match(/ez-consent-tcf/),EVAL_GOOGLE_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_HEMA_TEST_0:()=>document.cookie.includes("cookies_rejected=1"),EVAL_IUBENDA_0:()=>document.querySelectorAll(".purposes-item input[type=checkbox]:not([disabled])").forEach((e=>{e.checked&&e.click()}))||!0,EVAL_IUBENDA_1:()=>!!document.cookie.match(/_iub_cs-\d+=/),EVAL_IWINK_TEST:()=>document.cookie.includes("cookie_permission_granted=no"),EVAL_JQUERY_COOKIEBAR_0:()=>!document.cookie.includes("cookies-state=accepted"),EVAL_MEDIAVINE_0:()=>document.querySelectorAll('[data-name="mediavine-gdpr-cmp"] input[type=checkbox]').forEach((e=>e.checked&&e.click()))||!0,EVAL_MICROSOFT_0:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Reject|Ablehnen")))[0].click()||!0,EVAL_MICROSOFT_1:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Accept|Annehmen")))[0].click()||!0,EVAL_MICROSOFT_2:()=>!!document.cookie.match("MSCC|GHCC"),EVAL_MOOVE_0:()=>document.querySelectorAll("#moove_gdpr_cookie_modal input").forEach((e=>{e.disabled||"moove_gdpr_strict_cookies"===e.name||(e.checked=!1)}))||!0,EVAL_ONENINETWO_0:()=>document.cookie.includes("CC_ADVERTISING=NO")&&document.cookie.includes("CC_ANALYTICS=NO"),EVAL_OPERA_0:()=>document.cookie.includes("cookie_consent_essential=true")&&!document.cookie.includes("cookie_consent_marketing=true"),EVAL_PAYPAL_0:()=>!0===document.cookie.includes("cookie_prefs"),EVAL_PRIMEBOX_0:()=>!document.cookie.includes("cb-enabled=accepted"),EVAL_PUBTECH_0:()=>document.cookie.includes("euconsent-v2")&&(document.cookie.match(/.YAAAAAAAAAAA/)||document.cookie.match(/.aAAAAAAAAAAA/)||document.cookie.match(/.YAAACFgAAAAA/)),EVAL_REDDIT_0:()=>document.cookie.includes("eu_cookie={%22opted%22:true%2C%22nonessential%22:false}"),EVAL_SIBBO_0:()=>!!window.localStorage.getItem("euconsent-v2"),EVAL_SIRDATA_UNBLOCK_SCROLL:()=>(document.documentElement.classList.forEach((e=>{e.startsWith("sd-cmp-")&&document.documentElement.classList.remove(e)})),!0),EVAL_SNIGEL_0:()=>!!document.cookie.match("snconsent"),EVAL_STEAMPOWERED_0:()=>2===JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>e.trim().startsWith("cookieSettings"))).split("=")[1])).preference_state,EVAL_SVT_TEST:()=>document.cookie.includes('cookie-consent-1={"optedIn":true,"functionality":false,"statistics":false}'),EVAL_TAKEALOT_0:()=>document.body.classList.remove("freeze")||(document.body.style="")||!0,EVAL_TARTEAUCITRON_0:()=>tarteaucitron.userInterface.respondAll(!1)||!0,EVAL_TARTEAUCITRON_1:()=>tarteaucitron.userInterface.respondAll(!0)||!0,EVAL_TARTEAUCITRON_2:()=>document.cookie.match(/tarteaucitron=[^;]*/)[0].includes("false"),EVAL_TAUNTON_TEST:()=>document.cookie.includes("taunton_user_consent_submitted=true"),EVAL_TEALIUM_0:()=>void 0!==window.utag&&"object"==typeof utag.gdpr,EVAL_TEALIUM_1:()=>utag.gdpr.setConsentValue(!1)||!0,EVAL_TEALIUM_DONOTSELL:()=>utag.gdpr.dns?.setDnsState(!1)||!0,EVAL_TEALIUM_2:()=>utag.gdpr.setConsentValue(!0)||!0,EVAL_TEALIUM_3:()=>1!==utag.gdpr.getConsentState(),EVAL_TEALIUM_DONOTSELL_CHECK:()=>1!==utag.gdpr.dns?.getDnsState(),EVAL_TESTCMP_0:()=>"button_clicked"===window.results.results[0],EVAL_TESTCMP_COSMETIC_0:()=>"banner_hidden"===window.results.results[0],EVAL_THEFREEDICTIONARY_0:()=>cmpUi.showPurposes()||cmpUi.rejectAll()||!0,EVAL_THEFREEDICTIONARY_1:()=>cmpUi.allowAll()||!0,EVAL_THEVERGE_0:()=>document.cookie.includes("_duet_gdpr_acknowledged=1"),EVAL_UBUNTU_COM_0:()=>document.cookie.includes("_cookies_accepted=essential"),EVAL_UK_COOKIE_CONSENT_0:()=>!document.cookie.includes("catAccCookies"),EVAL_USERCENTRICS_API_0:()=>"object"==typeof UC_UI,EVAL_USERCENTRICS_API_1:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_2:()=>!!UC_UI.denyAllConsents(),EVAL_USERCENTRICS_API_3:()=>!!UC_UI.acceptAllConsents(),EVAL_USERCENTRICS_API_4:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_5:()=>!0===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_API_6:()=>!1===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_BUTTON_0:()=>JSON.parse(localStorage.getItem("usercentrics")).consents.every((e=>e.isEssential||!e.consentStatus)),EVAL_WAITROSE_0:()=>Array.from(document.querySelectorAll("label[id$=cookies-deny-label]")).forEach((e=>e.click()))||!0,EVAL_WAITROSE_1:()=>document.cookie.includes("wtr_cookies_advertising=0")&&document.cookie.includes("wtr_cookies_analytics=0"),EVAL_WP_COOKIE_NOTICE_0:()=>document.cookie.includes("wpl_viewed_cookie=no"),EVAL_XE_TEST:()=>document.cookie.includes("xeConsentState={%22performance%22:false%2C%22marketing%22:false%2C%22compliance%22:false}"),EVAL_XING_0:()=>document.cookie.includes("userConsent=%7B%22marketing%22%3Afalse"),EVAL_YOUTUBE_DESKTOP_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_YOUTUBE_MOBILE_0:()=>!!document.cookie.match(/SOCS=CAE/)};var l={main:!0,frame:!1,urlPattern:""},p=class{constructor(e){this.runContext=l,this.autoconsent=e}get hasSelfTest(){throw new Error("Not Implemented")}get isIntermediate(){throw new Error("Not Implemented")}get isCosmetic(){throw new Error("Not Implemented")}mainWorldEval(e){const t=r[e];if(!t)return console.warn("Snippet not found",e),Promise.resolve(!1);const o=this.autoconsent.config.logs;if(this.autoconsent.config.isMainWorld){o.evals&&console.log("inline eval:",e,t);let c=!1;try{c=!!t.call(globalThis)}catch(t){o.evals&&console.error("error evaluating rule",e,t)}return Promise.resolve(c)}const c=`(${t.toString()})()`;return o.evals&&console.log("async eval:",e,c),s(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...l,...this.runContext},t=window.top===window;return!(t&&!e.main)&&(!(!t&&!e.frame)&&!(e.urlPattern&&!window.location.href.match(e.urlPattern)))}detectCmp(){throw new Error("Not Implemented")}async detectPopup(){return!1}optOut(){throw new Error("Not Implemented")}optIn(){throw new Error("Not Implemented")}openCmp(){throw new Error("Not Implemented")}async test(){return Promise.resolve(!0)}click(e,t=!1){return this.autoconsent.domActions.click(e,t)}elementExists(e){return this.autoconsent.domActions.elementExists(e)}elementVisible(e,t){return this.autoconsent.domActions.elementVisible(e,t)}waitForElement(e,t){return this.autoconsent.domActions.waitForElement(e,t)}waitForVisible(e,t,o){return this.autoconsent.domActions.waitForVisible(e,t,o)}waitForThenClick(e,t,o){return this.autoconsent.domActions.waitForThenClick(e,t,o)}wait(e){return this.autoconsent.domActions.wait(e)}hide(e,t){return this.autoconsent.domActions.hide(e,t)}prehide(e){return this.autoconsent.domActions.prehide(e)}undoPrehide(){return this.autoconsent.domActions.undoPrehide()}querySingleReplySelector(e,t){return this.autoconsent.domActions.querySingleReplySelector(e,t)}querySelectorChain(e){return this.autoconsent.domActions.querySelectorChain(e)}elementSelector(e){return this.autoconsent.domActions.elementSelector(e)}},d=class extends p{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||l}get hasSelfTest(){return!!this.rule.test}get isIntermediate(){return!!this.rule.intermediate}get isCosmetic(){return!!this.rule.cosmetic}get prehideSelectors(){return this.rule.prehideSelectors}async detectCmp(){return!!this.rule.detectCmp&&this._runRulesParallel(this.rule.detectCmp)}async detectPopup(){return!!this.rule.detectPopup&&this._runRulesSequentially(this.rule.detectPopup)}async optOut(){const e=this.autoconsent.config.logs;return!!this.rule.optOut&&(e.lifecycle&&console.log("Initiated optOut()",this.rule.optOut),this._runRulesSequentially(this.rule.optOut))}async optIn(){const e=this.autoconsent.config.logs;return!!this.rule.optIn&&(e.lifecycle&&console.log("Initiated optIn()",this.rule.optIn),this._runRulesSequentially(this.rule.optIn))}async openCmp(){return!!this.rule.openCmp&&this._runRulesSequentially(this.rule.openCmp)}async test(){return this.hasSelfTest?this._runRulesSequentially(this.rule.test):super.test()}async evaluateRuleStep(e){const t=[],o=this.autoconsent.config.logs;if(e.exists&&t.push(this.elementExists(e.exists)),e.visible&&t.push(this.elementVisible(e.visible,e.check)),e.eval){const o=this.mainWorldEval(e.eval);t.push(o)}if(e.waitFor&&t.push(this.waitForElement(e.waitFor,e.timeout)),e.waitForVisible&&t.push(this.waitForVisible(e.waitForVisible,e.timeout,e.check)),e.click&&t.push(this.click(e.click,e.all)),e.waitForThenClick&&t.push(this.waitForThenClick(e.waitForThenClick,e.timeout,e.all)),e.wait&&t.push(this.wait(e.wait)),e.hide&&t.push(this.hide(e.hide,e.method)),e.if){if(!e.if.exists&&!e.if.visible)return console.error("invalid conditional rule",e.if),!1;const c=await this.evaluateRuleStep(e.if);o.rulesteps&&console.log("Condition is",c),c?t.push(this._runRulesSequentially(e.then)):e.else?t.push(this._runRulesSequentially(e.else)):t.push(!0)}if(e.any){for(const t of e.any)if(await this.evaluateRuleStep(t))return!0;return!1}if(0===t.length)return o.errors&&console.warn("Unrecognized rule",e),!1;return(await Promise.all(t)).reduce(((e,t)=>e&&t),!0)}async _runRulesParallel(e){const t=e.map((e=>this.evaluateRuleStep(e)));return(await Promise.all(t)).every((e=>!!e))}async _runRulesSequentially(e){const t=this.autoconsent.config.logs;for(const o of e){t.rulesteps&&console.log("Running rule...",o);const e=await this.evaluateRuleStep(o);if(t.rulesteps&&console.log("...rule result",e),!e&&!o.optional)return!1}return!0}};function u(e="autoconsent-css-rules"){const t=`style#${e}`,o=document.querySelector(t);if(o&&o instanceof HTMLStyleElement)return o;{const t=document.head||document.getElementsByTagName("head")[0]||document.documentElement,o=document.createElement("style");return o.id=e,t.appendChild(o),o}}function m(e,t,o="display"){const c=`${t} { ${"opacity"===o?"opacity: 0":"display: none"} !important; z-index: -1 !important; pointer-events: none !important; } `;return e instanceof HTMLStyleElement&&(e.innerText+=c,t.length>0)}async function h(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(h(e,t-1,o))}),o)})):Promise.resolve(c)}function k(e){if(!e)return!1;if(null!==e.offsetParent)return!0;{const t=window.getComputedStyle(e);if("fixed"===t.position&&"none"!==t.display)return!0}return!1}function b(e){const t={enabled:!0,autoAction:"optOut",disabledCmps:[],enablePrehide:!0,enableCosmeticRules:!0,detectRetries:20,isMainWorld:!1,prehideTimeout:2e3,logs:{lifecycle:!1,rulesteps:!1,evals:!1,errors:!0,messages:!1}},o=(c=t,globalThis.structuredClone?structuredClone(c):JSON.parse(JSON.stringify(c)));var c;for(const c of Object.keys(t))void 0!==e[c]&&(o[c]=e[c]);return o}var _="#truste-show-consent",g="#truste-consent-track",y=[class extends p{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${g}`],this.runContext={main:!0,frame:!1},this._shortcutButton=null,this._optInDone=!1}get hasSelfTest(){return!1}get isIntermediate(){return!this._optInDone&&!this._shortcutButton}get isCosmetic(){return!1}async detectCmp(){const e=this.elementExists(`${_},${g}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${g}`,"all")}openFrame(){this.click(_)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(m(u(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${g}`),this.click(_),setTimeout((()=>{u().remove()}),1e4),!0)}async optIn(){return this._optInDone=!0,this.click("#truste-consent-button")}async openCmp(){return!0}async test(){return await this.mainWorldEval("EVAL_TRUSTARC_TOP")}},class extends p{constructor(){super(...arguments),this.name="TrustArc-frame",this.runContext={main:!1,frame:!0,urlPattern:"^https://consent-pref\\.trustarc\\.com/\\?"}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return!0}async detectPopup(){return this.elementVisible("#defaultpreferencemanager","any")&&this.elementVisible(".mainContent","any")}async navigateToSettings(){return await h((async()=>this.elementExists(".shp")||this.elementVisible(".advance","any")||this.elementExists(".switch span:first-child")),10,500),this.elementExists(".shp")&&this.click(".shp"),await this.waitForElement(".prefPanel",5e3),this.elementVisible(".advance","any")&&this.click(".advance"),await h((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await h((()=>"complete"===document.readyState),20,100),await this.waitForElement(".mainContent[aria-hidden=false]",5e3),!!this.click(".rejectAll")||(this.elementExists(".prefPanel")&&await this.waitForElement('.prefPanel[style="visibility: visible;"]',3e3),this.click("#catDetails0")?(this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",5e3),!0):this.click(".required")?(this.waitForThenClick("#gwt-debug-close_id",5e3),!0):(await this.navigateToSettings(),this.click(".switch span:nth-child(1):not(.active)",!0),this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",3e5),!0))}async optIn(){return this.click(".call")||(await this.navigateToSettings(),this.click(".switch span:nth-child(2)",!0),this.click(".submit"),this.waitForElement("#gwt-debug-close_id",3e5).then((()=>{this.click("#gwt-debug-close_id")}))),!0}},class extends p{constructor(){super(...arguments),this.name="Cybotcookiebot",this.prehideSelectors=["#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#dtcookie-container,#cookiebanner,#cb-cookieoverlay,.modal--cookie-banner,#cookiebanner_outer,#CookieBanner"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return await this.mainWorldEval("EVAL_COOKIEBOT_1")}async detectPopup(){return this.mainWorldEval("EVAL_COOKIEBOT_2")}async optOut(){await this.wait(500);let e=await this.mainWorldEval("EVAL_COOKIEBOT_3");return await this.wait(500),e=e&&await this.mainWorldEval("EVAL_COOKIEBOT_4"),e}async optIn(){return this.elementExists("#dtcookie-container")?this.click(".h-dtcookie-accept"):(this.click(".CybotCookiebotDialogBodyLevelButton:not(:checked):enabled",!0),this.click("#CybotCookiebotDialogBodyLevelButtonAccept"),this.click("#CybotCookiebotDialogBodyButtonAccept"),!0)}async test(){return await this.wait(500),await this.mainWorldEval("EVAL_COOKIEBOT_5")}},class extends p{constructor(){super(...arguments),this.name="Sourcepoint-frame",this.prehideSelectors=["div[id^='sp_message_container_'],.message-overlay","#sp_privacy_manager_container"],this.ccpaNotice=!1,this.ccpaPopup=!1,this.runContext={main:!1,frame:!0}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){const e=new URL(location.href);return e.searchParams.has("message_id")&&"ccpa-notice.sp-prod.net"===e.hostname?(this.ccpaNotice=!0,!0):"ccpa-pm.sp-prod.net"===e.hostname?(this.ccpaPopup=!0,!0):("/index.html"===e.pathname||"/privacy-manager/index.html"===e.pathname||"/ccpa_pm/index.html"===e.pathname)&&(e.searchParams.has("message_id")||e.searchParams.has("requestUUID")||e.searchParams.has("consentUUID"))}async detectPopup(){return!!this.ccpaNotice||(this.ccpaPopup?await this.waitForElement(".priv-save-btn",2e3):(await this.waitForElement(".sp_choice_type_11,.sp_choice_type_12,.sp_choice_type_13,.sp_choice_type_ACCEPT_ALL,.sp_choice_type_SAVE_AND_EXIT",2e3),!this.elementExists(".sp_choice_type_9")))}async optIn(){return await this.waitForElement(".sp_choice_type_11,.sp_choice_type_ACCEPT_ALL",2e3),!!this.click(".sp_choice_type_11")||!!this.click(".sp_choice_type_ACCEPT_ALL")}isManagerOpen(){return"/privacy-manager/index.html"===location.pathname||"/ccpa_pm/index.html"===location.pathname}async optOut(){const e=this.autoconsent.config.logs;if(this.ccpaPopup){const e=document.querySelectorAll(".priv-purpose-container .sp-switch-arrow-block a.neutral.on .right");for(const t of e)t.click();const t=document.querySelectorAll(".priv-purpose-container .sp-switch-arrow-block a.switch-bg.on");for(const e of t)e.click();return this.click(".priv-save-btn")}if(!this.isManagerOpen()){if(!await this.waitForElement(".sp_choice_type_12,.sp_choice_type_13"))return!1;if(!this.elementExists(".sp_choice_type_12"))return this.click(".sp_choice_type_13");this.click(".sp_choice_type_12"),await h((()=>this.isManagerOpen()),200,100)}await this.waitForElement(".type-modal",2e4),this.waitForThenClick(".ccpa-stack .pm-switch[aria-checked=true] .slider",500,!0);try{const e=".sp_choice_type_REJECT_ALL",t=".reject-toggle",o=await Promise.race([this.waitForElement(e,2e3).then((e=>e?0:-1)),this.waitForElement(t,2e3).then((e=>e?1:-1)),this.waitForElement(".pm-features",2e3).then((e=>e?2:-1))]);if(0===o)return await this.wait(1500),this.click(e);1===o?this.click(t):2===o&&(await this.waitForElement(".pm-features",1e4),this.click(".checked > span",!0),this.click(".chevron"))}catch(t){e.errors&&console.warn(t)}return this.click(".sp_choice_type_SAVE_AND_EXIT")}},class extends p{constructor(){super(...arguments),this.name="consentmanager.net",this.prehideSelectors=["#cmpbox,#cmpbox2"],this.apiAvailable=!1}get hasSelfTest(){return this.apiAvailable}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.apiAvailable=await this.mainWorldEval("EVAL_CONSENTMANAGER_1"),!!this.apiAvailable||this.elementExists("#cmpbox")}async detectPopup(){return this.apiAvailable?(await this.wait(500),await this.mainWorldEval("EVAL_CONSENTMANAGER_2")):this.elementVisible("#cmpbox .cmpmore","any")}async optOut(){return await this.wait(500),this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_3"):!!this.click(".cmpboxbtnno")||(this.elementExists(".cmpwelcomeprpsbtn")?(this.click(".cmpwelcomeprpsbtn > a[aria-checked=true]",!0),this.click(".cmpboxbtnsave"),!0):(this.click(".cmpboxbtncustom"),await this.waitForElement(".cmptblbox",2e3),this.click(".cmptdchoice > a[aria-checked=true]",!0),this.click(".cmpboxbtnyescustomchoices"),this.hide("#cmpwrapper,#cmpbox","display"),!0))}async optIn(){return this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_4"):this.click(".cmpboxbtnyes")}async test(){if(this.apiAvailable)return await this.mainWorldEval("EVAL_CONSENTMANAGER_5")}},class extends p{constructor(){super(...arguments),this.name="Evidon"}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#_evidon_banner")}async detectPopup(){return this.elementVisible("#_evidon_banner","any")}async optOut(){return this.click("#_evidon-decline-button")||(m(u(),"#evidon-prefdiag-overlay,#evidon-prefdiag-background"),this.click("#_evidon-option-button"),await this.waitForElement("#evidon-prefdiag-overlay",5e3),this.click("#evidon-prefdiag-decline")),!0}async optIn(){return this.click("#_evidon-accept-button")}},class extends p{constructor(){super(...arguments),this.name="Onetrust",this.prehideSelectors=["#onetrust-banner-sdk,#onetrust-consent-sdk,.onetrust-pc-dark-filter,.js-consent-banner"],this.runContext={urlPattern:"^(?!.*https://www\\.nba\\.com/)"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#onetrust-banner-sdk,#onetrust-pc-sdk")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk,#onetrust-pc-sdk","any")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies"):(this.elementExists("#onetrust-pc-btn-handler")?this.click("#onetrust-pc-btn-handler"):this.click(".ot-sdk-show-settings,button.js-cookie-settings"),await this.waitForElement("#onetrust-consent-sdk",2e3),await this.wait(1e3),this.click("#onetrust-consent-sdk input.category-switch-handler:checked,.js-editor-toggle-state:checked",!0),await this.wait(1e3),await this.waitForElement(".save-preference-btn-handler,.js-consent-save",2e3),this.click(".save-preference-btn-handler,.js-consent-save"),await this.waitForVisible("#onetrust-banner-sdk",5e3,"none"),!0)}async optIn(){return this.click("#onetrust-accept-btn-handler,#accept-recommended-btn-handler,.js-accept-cookies")}async test(){return await h((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends p{constructor(){super(...arguments),this.name="Klaro",this.prehideSelectors=[".klaro"],this.settingsOpen=!1}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".klaro > .cookie-modal")?(this.settingsOpen=!0,!0):this.elementExists(".klaro > .cookie-notice")}async detectPopup(){return this.elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal","any")}async optOut(){return!!this.click(".klaro .cn-decline")||(this.settingsOpen||(this.click(".klaro .cn-learn-more,.klaro .cm-button-manage"),await this.waitForElement(".klaro > .cookie-modal",2e3),this.settingsOpen=!0),!!this.click(".klaro .cn-decline")||(this.click(".cm-purpose:not(.cm-toggle-all) > input:not(.half-checked,.required,.only-required),.cm-purpose:not(.cm-toggle-all) > div > input:not(.half-checked,.required,.only-required)",!0),this.click(".cm-btn-accept,.cm-button")))}async optIn(){return!!this.click(".klaro .cm-btn-accept-all")||(this.settingsOpen?(this.click(".cm-purpose:not(.cm-toggle-all) > input.half-checked",!0),this.click(".cm-btn-accept")):this.click(".klaro .cookie-notice .cm-btn-success"))}async test(){return await this.mainWorldEval("EVAL_KLARO_1")}},class extends p{constructor(){super(...arguments),this.name="Uniconsent"}get prehideSelectors(){return[".unic",".modal:has(.unic)"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".unic .unic-box,.unic .unic-bar,.unic .unic-modal")}async detectPopup(){return this.elementVisible(".unic .unic-box,.unic .unic-bar,.unic .unic-modal","any")}async optOut(){if(await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic button").forEach((e=>{const t=e.textContent;(t.includes("Manage Options")||t.includes("Optionen verwalten"))&&e.click()})),await this.waitForElement(".unic input[type=checkbox]",1e3)){await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic input[type=checkbox]").forEach((e=>{e.checked&&e.click()}));for(const e of document.querySelectorAll(".unic button")){const t=e.textContent;for(const o of["Confirm Choices","Save Choices","Auswahl speichern"])if(t.includes(o))return e.click(),await this.wait(500),!0}}return!1}async optIn(){return this.waitForThenClick(".unic #unic-agree")}async test(){await this.wait(1e3);return!this.elementExists(".unic .unic-box,.unic .unic-bar")}},class extends p{constructor(){super(...arguments),this.prehideSelectors=[".cmp-root"],this.name="Conversant"}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".cmp-root .cmp-receptacle")}async detectPopup(){return this.elementVisible(".cmp-root .cmp-receptacle","any")}async optOut(){if(!await this.waitForThenClick(".cmp-main-button:not(.cmp-main-button--primary)"))return!1;if(!await this.waitForElement(".cmp-view-tab-tabs"))return!1;await this.waitForThenClick(".cmp-view-tab-tabs > :first-child"),await this.waitForThenClick(".cmp-view-tab-tabs > .cmp-view-tab--active:first-child");for(const e of Array.from(document.querySelectorAll(".cmp-accordion-item"))){e.querySelector(".cmp-accordion-item-title").click(),await h((()=>!!e.querySelector(".cmp-accordion-item-content.cmp-active")),10,50);const t=e.querySelector(".cmp-accordion-item-content.cmp-active");t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e=>e.click())),t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e=>e.click()))}return await this.click(".cmp-main-button:not(.cmp-main-button--primary)"),!0}async optIn(){return this.waitForThenClick(".cmp-main-button.cmp-main-button--primary")}async test(){return document.cookie.includes("cmp-data=0")}},class extends p{constructor(){super(...arguments),this.name="tiktok.com",this.runContext={urlPattern:"tiktok"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}getShadowRoot(){const e=document.querySelector("tiktok-cookie-banner");return e?e.shadowRoot:null}async detectCmp(){return this.elementExists("tiktok-cookie-banner")}async detectPopup(){return k(this.getShadowRoot().querySelector(".tiktok-cookie-banner"))}async optOut(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:first-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no decline button found"),!1)}async optIn(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:last-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no accept button found"),!1)}async test(){const e=document.cookie.match(/cookie-consent=([^;]+)/);if(!e)return!1;const t=JSON.parse(decodeURIComponent(e[1]));return Object.values(t).every((e=>"boolean"!=typeof e||!1===e))}},class extends p{constructor(){super(...arguments),this.runContext={urlPattern:"^https://(www\\.)?airbnb\\.[^/]+/"},this.prehideSelectors=["div[data-testid=main-cookies-banner-container]",'div:has(> div:first-child):has(> div:last-child):has(> section [data-testid="strictly-necessary-cookies"])']}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("div[data-testid=main-cookies-banner-container]")}async detectPopup(){return this.elementVisible("div[data-testid=main-cookies-banner-container","any")}async optOut(){let e;for(await this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._snbhip0");e=document.querySelector("[data-testid=modal-container] button[aria-checked=true]:not([disabled])");)e.click();return this.waitForThenClick("button[data-testid=save-btn]")}async optIn(){return this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._148dgdpk")}async test(){return await h((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}}];var w=[{name:"192.com",detectCmp:[{exists:".ont-cookies"}],detectPopup:[{visible:".ont-cookies"}],optIn:[{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-ok2"}],optOut:[{click:".ont-cookes-btn-manage"},{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-choose"}],test:[{eval:"EVAL_ONENINETWO_0"}]},{name:"1password-com",cosmetic:!0,prehideSelectors:['footer #footer-root [aria-label="Cookie Consent"]'],detectCmp:[{exists:'footer #footer-root [aria-label="Cookie Consent"]'}],detectPopup:[{visible:'footer #footer-root [aria-label="Cookie Consent"]'}],optIn:[{click:'footer #footer-root [aria-label="Cookie Consent"] button'}],optOut:[{hide:'footer #footer-root [aria-label="Cookie Consent"]'}]},{name:"abconcerts.be",vendorUrl:"https://unknown",intermediate:!1,prehideSelectors:["dialog.cookie-consent"],detectCmp:[{exists:"dialog.cookie-consent form.cookie-consent__form"}],detectPopup:[{visible:"dialog.cookie-consent form.cookie-consent__form"}],optIn:[{waitForThenClick:"dialog.cookie-consent form.cookie-consent__form button[value=yes]"}],optOut:[{if:{exists:"dialog.cookie-consent form.cookie-consent__form button[value=no]"},then:[{click:"dialog.cookie-consent form.cookie-consent__form button[value=no]"}],else:[{click:"dialog.cookie-consent form.cookie-consent__form button.cookie-consent__options-toggle"},{waitForThenClick:'dialog.cookie-consent form.cookie-consent__form button[value="save_options"]'}]}]},{name:"activobank.pt",runContext:{urlPattern:"^https://(www\\.)?activobank\\.pt"},prehideSelectors:["aside#cookies,.overlay-cookies"],detectCmp:[{exists:"#cookies .cookies-btn"}],detectPopup:[{visible:"#cookies #submitCookies"}],optIn:[{waitForThenClick:"#cookies #submitCookies"}],optOut:[{waitForThenClick:"#cookies #rejectCookies"}]},{name:"Adroll",prehideSelectors:["#adroll_consent_container"],detectCmp:[{exists:"#adroll_consent_container"}],detectPopup:[{visible:"#adroll_consent_container"}],optIn:[{waitForThenClick:"#adroll_consent_accept"}],optOut:[{waitForThenClick:"#adroll_consent_reject"}],test:[{eval:"EVAL_ADROLL_0"}]},{name:"affinity.serif.com",detectCmp:[{exists:".c-cookie-banner button[data-qa='allow-all-cookies']"}],detectPopup:[{visible:".c-cookie-banner"}],optIn:[{click:'button[data-qa="allow-all-cookies"]'}],optOut:[{click:'button[data-qa="manage-cookies"]'},{waitFor:'.c-cookie-banner ~ [role="dialog"]'},{waitForThenClick:'.c-cookie-banner ~ [role="dialog"] input[type="checkbox"][value="true"]',all:!0},{click:'.c-cookie-banner ~ [role="dialog"] .c-modal__action button'}],test:[{wait:500},{eval:"EVAL_AFFINITY_SERIF_COM_0"}]},{name:"agolde.com",cosmetic:!0,prehideSelectors:["#modal-1 div[data-micromodal-close]"],detectCmp:[{exists:"#modal-1 div[aria-labelledby=modal-1-title]"}],detectPopup:[{exists:"#modal-1 div[data-micromodal-close]"}],optIn:[{click:'button[aria-label="Close modal"]'}],optOut:[{hide:"#modal-1 div[data-micromodal-close]"}]},{name:"aliexpress",vendorUrl:"https://aliexpress.com/",runContext:{urlPattern:"^https://.*\\.aliexpress\\.com/"},prehideSelectors:["#gdpr-new-container"],detectCmp:[{exists:"#gdpr-new-container"}],detectPopup:[{visible:"#gdpr-new-container"}],optIn:[{waitForThenClick:"#gdpr-new-container .btn-accept"}],optOut:[{waitForThenClick:"#gdpr-new-container .btn-more"},{waitFor:"#gdpr-new-container .gdpr-dialog-switcher"},{click:"#gdpr-new-container .switcher-on",all:!0,optional:!0},{click:"#gdpr-new-container .btn-save"}]},{name:"almacmp",prehideSelectors:["#alma-cmpv2-container"],detectCmp:[{exists:"#alma-cmpv2-container"}],detectPopup:[{visible:"#alma-cmpv2-container #almacmp-modal-layer1"}],optIn:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalConfirmBtn"}],optOut:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalSettingBtn"},{waitFor:"#alma-cmpv2-container #almacmp-modal-layer2"},{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer2 #almacmp-reject-all-layer2"}],test:[{eval:"EVAL_ALMACMP_0"}]},{name:"altium.com",cosmetic:!0,prehideSelectors:[".altium-privacy-bar"],detectCmp:[{exists:".altium-privacy-bar"}],detectPopup:[{exists:".altium-privacy-bar"}],optIn:[{click:"a.altium-privacy-bar__btn"}],optOut:[{hide:".altium-privacy-bar"}]},{name:"amazon.com",prehideSelectors:['span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'],detectCmp:[{exists:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],detectPopup:[{visible:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],optIn:[{waitForVisible:"#sp-cc-accept"},{wait:500},{click:"#sp-cc-accept"}],optOut:[{waitForVisible:"#sp-cc-rejectall-link"},{wait:500},{click:"#sp-cc-rejectall-link"}]},{name:"aquasana.com",cosmetic:!0,prehideSelectors:["#consent-tracking"],detectCmp:[{exists:"#consent-tracking"}],detectPopup:[{exists:"#consent-tracking"}],optIn:[{click:"#accept_consent"}],optOut:[{hide:"#consent-tracking"}]},{name:"arbeitsagentur",vendorUrl:"https://www.arbeitsagentur.de/",prehideSelectors:[".modal-open bahf-cookie-disclaimer-dpl3"],detectCmp:[{exists:"bahf-cookie-disclaimer-dpl3"}],detectPopup:[{visible:"bahf-cookie-disclaimer-dpl3"}],optIn:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-primary"]}],optOut:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-contrast"]}],test:[{eval:"EVAL_ARBEITSAGENTUR_TEST"}]},{name:"asus",vendorUrl:"https://www.asus.com/",runContext:{urlPattern:"^https://www\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info,#cookie-policy-info-bg"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{waitForThenClick:'#cookie-policy-info [data-agree="Accept Cookies"]'}],optOut:[{if:{exists:"#cookie-policy-info .btn-reject"},then:[{waitForThenClick:"#cookie-policy-info .btn-reject"}],else:[{waitForThenClick:"#cookie-policy-info .btn-setting"},{waitForThenClick:'#cookie-policy-lightbox-wrapper [data-agree="Save Settings"]'}]}]},{name:"athlinks-com",runContext:{urlPattern:"^https://(www\\.)?athlinks\\.com/"},cosmetic:!0,prehideSelectors:["#footer-container ~ div"],detectCmp:[{exists:"#footer-container ~ div"}],detectPopup:[{visible:"#footer-container > div"}],optIn:[{click:"#footer-container ~ div button"}],optOut:[{hide:"#footer-container ~ div"}]},{name:"ausopen.com",cosmetic:!0,detectCmp:[{exists:".gdpr-popup__message"}],detectPopup:[{visible:".gdpr-popup__message"}],optOut:[{hide:".gdpr-popup__message"}],optIn:[{click:".gdpr-popup__message button"}]},{name:"automattic-cmp-optout",prehideSelectors:['form[class*="cookie-banner"][method="post"]'],detectCmp:[{exists:'form[class*="cookie-banner"][method="post"]'}],detectPopup:[{visible:'form[class*="cookie-banner"][method="post"]'}],optIn:[{click:'a[class*="accept-all-button"]'}],optOut:[{click:'form[class*="cookie-banner"] div[class*="simple-options"] a[class*="customize-button"]'},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:'a[class*="accept-selection-button"]'}]},{name:"aws.amazon.com",prehideSelectors:["#awsccc-cb-content","#awsccc-cs-container","#awsccc-cs-modalOverlay","#awsccc-cs-container-inner"],detectCmp:[{exists:"#awsccc-cb-content"}],detectPopup:[{visible:"#awsccc-cb-content"}],optIn:[{click:"button[data-id=awsccc-cb-btn-accept"}],optOut:[{click:"button[data-id=awsccc-cb-btn-customize]"},{waitFor:"input[aria-checked]"},{click:"input[aria-checked=true]",all:!0,optional:!0},{click:"button[data-id=awsccc-cs-btn-save]"}]},{name:"axeptio",prehideSelectors:[".axeptio_widget"],detectCmp:[{exists:".axeptio_widget"}],detectPopup:[{visible:".axeptio_widget"}],optIn:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_acceptAll"}],optOut:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_dismiss"}],test:[{eval:"EVAL_AXEPTIO_0"}]},{name:"baden-wuerttemberg.de",prehideSelectors:[".cookie-alert.t-dark"],cosmetic:!0,detectCmp:[{exists:".cookie-alert.t-dark"}],detectPopup:[{visible:".cookie-alert.t-dark"}],optIn:[{click:".cookie-alert__form input:not([disabled]):not([checked])"},{click:".cookie-alert__button button"}],optOut:[{hide:".cookie-alert.t-dark"}]},{name:"bahn-de",vendorUrl:"https://www.bahn.de/",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?bahn\\.de/"},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:["body > div:first-child","#consent-layer"]}],detectPopup:[{visible:["body > div:first-child","#consent-layer"]}],optIn:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-all-cookies"]}],optOut:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-essential-cookies"]}],test:[{eval:"EVAL_BAHN_TEST"}]},{name:"bbb.org",runContext:{urlPattern:"^https://www\\.bbb\\.org/"},cosmetic:!0,prehideSelectors:['div[aria-label="use of cookies on bbb.org"]'],detectCmp:[{exists:'div[aria-label="use of cookies on bbb.org"]'}],detectPopup:[{visible:'div[aria-label="use of cookies on bbb.org"]'}],optIn:[{click:'div[aria-label="use of cookies on bbb.org"] button.bds-button-unstyled span.visually-hidden'}],optOut:[{hide:'div[aria-label="use of cookies on bbb.org"]'}]},{name:"bing.com",prehideSelectors:["#bnp_container"],detectCmp:[{exists:"#bnp_cookie_banner"}],detectPopup:[{visible:"#bnp_cookie_banner"}],optIn:[{click:"#bnp_btn_accept"}],optOut:[{click:"#bnp_btn_preference"},{click:"#mcp_savesettings"}],test:[{eval:"EVAL_BING_0"}]},{name:"blocksy",vendorUrl:"https://creativethemes.com/blocksy/docs/extensions/cookies-consent/",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[".cookie-notification"],detectCmp:[{exists:"#blocksy-ext-cookies-consent-styles-css"}],detectPopup:[{visible:".cookie-notification"}],optIn:[{click:".cookie-notification .ct-cookies-decline-button"}],optOut:[{waitForThenClick:".cookie-notification .ct-cookies-decline-button"}],test:[{eval:"EVAL_BLOCKSY_0"}]},{name:"borlabs",detectCmp:[{exists:"._brlbs-block-content"}],detectPopup:[{visible:"._brlbs-bar-wrap,._brlbs-box-wrap"}],optIn:[{click:"a[data-cookie-accept-all]"}],optOut:[{click:"a[data-cookie-individual]"},{waitForVisible:".cookie-preference"},{click:"input[data-borlabs-cookie-checkbox]:checked",all:!0,optional:!0},{click:"#CookiePrefSave"},{wait:500}],prehideSelectors:["#BorlabsCookieBox"],test:[{eval:"EVAL_BORLABS_0"}]},{name:"bundesregierung.de",prehideSelectors:[".bpa-cookie-banner"],detectCmp:[{exists:".bpa-cookie-banner"}],detectPopup:[{visible:".bpa-cookie-banner .bpa-module-full-hero"}],optIn:[{click:".bpa-accept-all-button"}],optOut:[{wait:500,comment:"click is not immediately recognized"},{waitForThenClick:".bpa-close-button"}],test:[{eval:"EVAL_BUNDESREGIERUNG_DE_0"}]},{name:"burpee.com",cosmetic:!0,prehideSelectors:["#notice-cookie-block"],detectCmp:[{exists:"#notice-cookie-block"}],detectPopup:[{exists:"#html-body #notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{hide:"#html-body #notice-cookie-block, #notice-cookie"}]},{name:"canva.com",prehideSelectors:['div[role="dialog"] a[data-anchor-id="cookie-policy"]'],detectCmp:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],detectPopup:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],optIn:[{click:'div[role="dialog"] button:nth-child(1)'}],optOut:[{if:{exists:'div[role="dialog"] button:nth-child(3)'},then:[{click:'div[role="dialog"] button:nth-child(2)'}],else:[{click:'div[role="dialog"] button:nth-child(2)'},{waitFor:'div[role="dialog"] a[data-anchor-id="privacy-policy"]'},{click:'div[role="dialog"] button:nth-child(2)'},{click:'div[role="dialog"] div:last-child button:only-child'}]}],test:[{eval:"EVAL_CANVA_0"}]},{name:"canyon.com",runContext:{urlPattern:"^https://www\\.canyon\\.com/"},prehideSelectors:["div.modal.cookiesModal.is-open"],detectCmp:[{exists:"div.modal.cookiesModal.is-open"}],detectPopup:[{visible:"div.modal.cookiesModal.is-open"}],optIn:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-submit"]'}],optOut:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-manage-cookies"]'},{waitForThenClick:"button#js-manage-data-privacy-save-button"}]},{name:"cc-banner-springer",prehideSelectors:[".cc-banner[data-cc-banner]"],detectCmp:[{exists:".cc-banner[data-cc-banner]"}],detectPopup:[{visible:".cc-banner[data-cc-banner]"}],optIn:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=accept]"}],optOut:[{if:{exists:".cc-banner[data-cc-banner] button[data-cc-action=reject]"},then:[{click:".cc-banner[data-cc-banner] button[data-cc-action=reject]"}],else:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=preferences]"},{waitFor:".cc-preferences[data-cc-preferences]"},{click:".cc-preferences[data-cc-preferences] input[type=radio][data-cc-action=toggle-category][value=off]",all:!0,optional:!0},{if:{exists:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"},then:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"}],else:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=save]"}]}]}],test:[{eval:"EVAL_CC_BANNER2_0"}]},{name:"cc_banner",cosmetic:!0,prehideSelectors:[".cc_banner-wrapper"],detectCmp:[{exists:".cc_banner-wrapper"}],detectPopup:[{visible:".cc_banner"}],optIn:[{click:".cc_btn_accept_all"}],optOut:[{hide:".cc_banner-wrapper"}]},{name:"ciaopeople.it",prehideSelectors:["#cp-gdpr-choices"],detectCmp:[{exists:"#cp-gdpr-choices"}],detectPopup:[{visible:"#cp-gdpr-choices"}],optIn:[{waitForThenClick:".gdpr-btm__right > button:nth-child(2)"}],optOut:[{waitForThenClick:".gdpr-top-content > button"},{waitFor:".gdpr-top-back"},{waitForThenClick:".gdpr-btm__right > button:nth-child(1)"}],test:[{visible:"#cp-gdpr-choices",check:"none"}]},{vendorUrl:"https://www.civicuk.com/cookie-control/",name:"civic-cookie-control",prehideSelectors:["#ccc-module,#ccc-overlay"],detectCmp:[{exists:"#ccc-module"}],detectPopup:[{visible:"#ccc"},{visible:"#ccc-module"}],optOut:[{click:"#ccc-reject-settings"}],optIn:[{click:"#ccc-recommended-settings"}]},{name:"click.io",prehideSelectors:["#cl-consent"],detectCmp:[{exists:"#cl-consent"}],detectPopup:[{visible:"#cl-consent"}],optIn:[{waitForThenClick:'#cl-consent [data-role="b_agree"]'}],optOut:[{waitFor:'#cl-consent [data-role="b_options"]'},{wait:500},{click:'#cl-consent [data-role="b_options"]'},{waitFor:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]'},{click:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]',all:!0},{click:'[data-role="b_save"]'}],test:[{eval:"EVAL_CLICKIO_0",comment:"TODO: this only checks if we interacted at all"}]},{name:"clinch",intermediate:!1,runContext:{frame:!1,main:!0},prehideSelectors:[".consent-modal[role=dialog]"],detectCmp:[{exists:".consent-modal[role=dialog]"}],detectPopup:[{visible:".consent-modal[role=dialog]"}],optIn:[{click:"#consent_agree"}],optOut:[{if:{exists:"#consent_reject"},then:[{click:"#consent_reject"}],else:[{click:"#manage_cookie_preferences"},{click:"#cookie_consent_preferences input:checked",all:!0,optional:!0},{click:"#consent_save"}]}],test:[{eval:"EVAL_CLINCH_0"}]},{name:"clustrmaps.com",runContext:{urlPattern:"^https://(www\\.)?clustrmaps\\.com/"},cosmetic:!0,prehideSelectors:["#gdpr-cookie-message"],detectCmp:[{exists:"#gdpr-cookie-message"}],detectPopup:[{visible:"#gdpr-cookie-message"}],optIn:[{click:"button#gdpr-cookie-accept"}],optOut:[{hide:"#gdpr-cookie-message"}]},{name:"coinbase",intermediate:!1,runContext:{frame:!0,main:!0,urlPattern:"^https://(www|help)\\.coinbase\\.com"},prehideSelectors:[],detectCmp:[{exists:"div[class^=CookieBannerContent__Container]"}],detectPopup:[{visible:"div[class^=CookieBannerContent__Container]"}],optIn:[{click:"div[class^=CookieBannerContent__CTA] :nth-last-child(1)"}],optOut:[{click:"button[class^=CookieBannerContent__Settings]"},{click:"div[class^=CookiePreferencesModal__CategoryContainer] input:checked",all:!0,optional:!0},{click:"div[class^=CookiePreferencesModal__ButtonContainer] > button"}],test:[{eval:"EVAL_COINBASE_0"}]},{name:"Complianz banner",prehideSelectors:["#cmplz-cookiebanner-container"],detectCmp:[{exists:"#cmplz-cookiebanner-container .cmplz-cookiebanner"}],detectPopup:[{visible:"#cmplz-cookiebanner-container .cmplz-cookiebanner",check:"any"}],optIn:[{waitForThenClick:".cmplz-cookiebanner .cmplz-accept"}],optOut:[{waitForThenClick:".cmplz-cookiebanner .cmplz-deny"}],test:[{eval:"EVAL_COMPLIANZ_BANNER_0"}]},{name:"Complianz categories",prehideSelectors:['.cc-type-categories[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"] .cc-dismiss'},then:[{click:".cc-dismiss"}],else:[{click:".cc-type-categories input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-save"}]}]},{name:"Complianz notice",prehideSelectors:['.cc-type-info[aria-describedby="cookieconsent:desc"]'],cosmetic:!0,detectCmp:[{exists:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],detectPopup:[{visible:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{if:{exists:".cc-deny"},then:[{click:".cc-deny"}],else:[{hide:'[aria-describedby="cookieconsent:desc"]'}]}]},{name:"Complianz opt-both",prehideSelectors:['[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'],detectCmp:[{exists:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],detectPopup:[{visible:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{waitForThenClick:".cc-deny"}]},{name:"Complianz optin",prehideSelectors:['.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{visible:".cc-deny"},then:[{click:".cc-deny"}],else:[{if:{visible:".cc-settings"},then:[{waitForThenClick:".cc-settings"},{waitForVisible:".cc-settings-view"},{click:".cc-settings-view input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-settings-view .cc-btn-accept-selected"}],else:[{click:".cc-dismiss"}]}]}]},{name:"cookie-law-info",prehideSelectors:["#cookie-law-info-bar"],detectCmp:[{exists:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_DETECT"}],detectPopup:[{visible:"#cookie-law-info-bar"}],optIn:[{click:'[data-cli_action="accept_all"]'}],optOut:[{hide:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_0"}],test:[{eval:"EVAL_COOKIE_LAW_INFO_1"}]},{name:"cookie-manager-popup",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,detectCmp:[{exists:"#notice-cookie-block #allow-functional-cookies, #notice-cookie-block #btn-cookie-settings"}],detectPopup:[{visible:"#notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{if:{exists:"#allow-functional-cookies"},then:[{click:"#allow-functional-cookies"}],else:[{waitForThenClick:"#btn-cookie-settings"},{waitForVisible:".modal-body"},{click:'.modal-body input:checked, .switch[data-switch="on"]',all:!0,optional:!0},{click:'[role="dialog"] .modal-footer button'}]}],prehideSelectors:["#btn-cookie-settings"],test:[{eval:"EVAL_COOKIE_MANAGER_POPUP_0"}]},{name:"cookie-notice",prehideSelectors:["#cookie-notice"],cosmetic:!0,detectCmp:[{visible:"#cookie-notice .cookie-notice-container"}],detectPopup:[{visible:"#cookie-notice"}],optIn:[{click:"#cn-accept-cookie"}],optOut:[{hide:"#cookie-notice"}]},{name:"cookie-script",vendorUrl:"https://cookie-script.com/",prehideSelectors:["#cookiescript_injected"],detectCmp:[{exists:"#cookiescript_injected"}],detectPopup:[{visible:"#cookiescript_injected"}],optOut:[{click:"#cookiescript_reject"}],optIn:[{click:"#cookiescript_accept"}]},{name:"cookieacceptbar",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#cookieAcceptBar.cookieAcceptBar"],detectCmp:[{exists:"#cookieAcceptBar.cookieAcceptBar"}],detectPopup:[{visible:"#cookieAcceptBar.cookieAcceptBar"}],optIn:[{waitForThenClick:"#cookieAcceptBarConfirm"}],optOut:[{hide:"#cookieAcceptBar.cookieAcceptBar"}]},{name:"cookiealert",intermediate:!1,prehideSelectors:[],runContext:{frame:!0,main:!0},detectCmp:[{exists:".cookie-alert-extended"}],detectPopup:[{visible:".cookie-alert-extended-modal"}],optIn:[{click:"button[data-controller='cookie-alert/extended/button/accept']"},{eval:"EVAL_COOKIEALERT_0"}],optOut:[{click:"a[data-controller='cookie-alert/extended/detail-link']"},{click:".cookie-alert-configuration-input:checked",all:!0,optional:!0},{click:"button[data-controller='cookie-alert/extended/button/configuration']"},{eval:"EVAL_COOKIEALERT_0"}],test:[{eval:"EVAL_COOKIEALERT_2"}]},{name:"cookieconsent2",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v2.x.x of the library",prehideSelectors:["#cc--main"],detectCmp:[{exists:"#cc--main"}],detectPopup:[{visible:"#cm"},{exists:"#s-all-bn"}],optIn:[{waitForThenClick:"#s-all-bn"}],optOut:[{waitForThenClick:"#s-rall-bn"}],test:[{eval:"EVAL_COOKIECONSENT2_TEST"}]},{name:"cookieconsent3",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v3.x.x of the library",prehideSelectors:["#cc-main"],detectCmp:[{exists:"#cc-main"}],detectPopup:[{visible:"#cc-main .cm-wrapper"}],optIn:[{waitForThenClick:".cm__btn[data-role=all]"}],optOut:[{waitForThenClick:".cm__btn[data-role=necessary]"}],test:[{eval:"EVAL_COOKIECONSENT3_TEST"}]},{name:"cookiefirst.com",prehideSelectors:["#cookiefirst-root,.cookiefirst-root,[aria-labelledby=cookie-preference-panel-title]"],detectCmp:[{exists:"#cookiefirst-root,.cookiefirst-root"}],detectPopup:[{visible:"#cookiefirst-root,.cookiefirst-root"}],optIn:[{click:"button[data-cookiefirst-action=accept]"}],optOut:[{if:{exists:"button[data-cookiefirst-action=adjust]"},then:[{click:"button[data-cookiefirst-action=adjust]"},{waitForVisible:"[data-cookiefirst-widget=modal]",timeout:1e3},{eval:"EVAL_COOKIEFIRST_1"},{wait:1e3},{click:"button[data-cookiefirst-action=save]"}],else:[{click:"button[data-cookiefirst-action=reject]"}]}],test:[{eval:"EVAL_COOKIEFIRST_0"}]},{name:"Cookie Information Banner",prehideSelectors:["#cookie-information-template-wrapper"],detectCmp:[{exists:"#cookie-information-template-wrapper"}],detectPopup:[{visible:"#cookie-information-template-wrapper"}],optIn:[{eval:"EVAL_COOKIEINFORMATION_1"}],optOut:[{hide:"#cookie-information-template-wrapper",comment:"some templates don't hide the banner automatically"},{eval:"EVAL_COOKIEINFORMATION_0"}],test:[{eval:"EVAL_COOKIEINFORMATION_2"}]},{name:"cookieyes",prehideSelectors:[".cky-overlay,.cky-consent-container"],detectCmp:[{exists:".cky-consent-container"}],detectPopup:[{visible:".cky-consent-container"}],optIn:[{waitForThenClick:".cky-consent-container [data-cky-tag=accept-button]"}],optOut:[{if:{exists:".cky-consent-container [data-cky-tag=reject-button]"},then:[{waitForThenClick:".cky-consent-container [data-cky-tag=reject-button]"}],else:[{if:{exists:".cky-consent-container [data-cky-tag=settings-button]"},then:[{click:".cky-consent-container [data-cky-tag=settings-button]"},{waitFor:".cky-modal-open input[type=checkbox]"},{click:".cky-modal-open input[type=checkbox]:checked",all:!0,optional:!0},{waitForThenClick:".cky-modal [data-cky-tag=detail-save-button]"}],else:[{hide:".cky-consent-container,.cky-overlay"}]}]}],test:[{eval:"EVAL_COOKIEYES_0"}]},{name:"corona-in-zahlen.de",prehideSelectors:[".cookiealert"],detectCmp:[{exists:".cookiealert"}],detectPopup:[{visible:".cookiealert"}],optOut:[{click:".configurecookies"},{click:".confirmcookies"}],optIn:[{click:".acceptcookies"}]},{name:"crossfit-com",cosmetic:!0,prehideSelectors:['body #modal > div > div[class^="_wrapper_"]'],detectCmp:[{exists:'body #modal > div > div[class^="_wrapper_"]'}],detectPopup:[{visible:'body #modal > div > div[class^="_wrapper_"]'}],optIn:[{click:'button[aria-label="accept cookie policy"]'}],optOut:[{hide:'body #modal > div > div[class^="_wrapper_"]'}]},{name:"csu-landtag-de",runContext:{urlPattern:"^https://(www|)?\\.csu-landtag\\.de"},prehideSelectors:["#cookie-disclaimer"],detectCmp:[{exists:"#cookie-disclaimer"}],detectPopup:[{visible:"#cookie-disclaimer"}],optIn:[{click:"#cookieall"}],optOut:[{click:"#cookiesel"}]},{name:"dailymotion-us",cosmetic:!0,prehideSelectors:['div[class*="CookiePopup__desktopContainer"]:has(div[class*="CookiePopup"])'],detectCmp:[{exists:'div[class*="CookiePopup__desktopContainer"]'}],detectPopup:[{visible:'div[class*="CookiePopup__desktopContainer"]'}],optIn:[{click:'div[class*="CookiePopup__desktopContainer"] > button > span'}],optOut:[{hide:'div[class*="CookiePopup__desktopContainer"]'}]},{name:"dailymotion.com",runContext:{urlPattern:"^https://(www\\.)?dailymotion\\.com/"},prehideSelectors:['div[class*="Overlay__container"]:has(div[class*="TCF2Popup"])'],detectCmp:[{exists:'div[class*="TCF2Popup"]'}],detectPopup:[{visible:'[class*="TCF2Popup"] a[href^="https://www.dailymotion.com/legal/cookiemanagement"]'}],optIn:[{waitForThenClick:'button[class*="TCF2Popup__button"]:not([class*="TCF2Popup__personalize"])'}],optOut:[{waitForThenClick:'button[class*="TCF2ContinueWithoutAcceptingButton"]'}],test:[{eval:"EVAL_DAILYMOTION_0"}]},{name:"deepl.com",prehideSelectors:[".dl_cookieBanner_container"],detectCmp:[{exists:".dl_cookieBanner_container"}],detectPopup:[{visible:".dl_cookieBanner_container"}],optOut:[{click:".dl_cookieBanner--buttonSelected"}],optIn:[{click:".dl_cookieBanner--buttonAll"}]},{name:"delta.com",runContext:{urlPattern:"^https://www\\.delta\\.com/"},cosmetic:!0,prehideSelectors:["ngc-cookie-banner"],detectCmp:[{exists:"div.cookie-footer-container"}],detectPopup:[{visible:"div.cookie-footer-container"}],optIn:[{click:" button.cookie-close-icon"}],optOut:[{hide:"div.cookie-footer-container"}]},{name:"dmgmedia-us",prehideSelectors:["#mol-ads-cmp-iframe, div.mol-ads-cmp > form > div"],detectCmp:[{exists:"div.mol-ads-cmp > form > div"}],detectPopup:[{waitForVisible:"div.mol-ads-cmp > form > div"}],optIn:[{waitForThenClick:"button.mol-ads-cmp--btn-primary"}],optOut:[{waitForThenClick:"div.mol-ads-ccpa--message > u > a"},{waitForVisible:".mol-ads-cmp--modal-dialog"},{waitForThenClick:"a.mol-ads-cmp-footer-privacy"},{waitForThenClick:"button.mol-ads-cmp--btn-secondary"}]},{name:"dmgmedia",prehideSelectors:['[data-project="mol-fe-cmp"]'],detectCmp:[{exists:'[data-project="mol-fe-cmp"]'}],detectPopup:[{visible:'[data-project="mol-fe-cmp"]'}],optIn:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=primary]'}],optOut:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=basic]'},{waitForVisible:'[data-project="mol-fe-cmp"] div[class*="tabContent"]'},{waitForThenClick:'[data-project="mol-fe-cmp"] div[class*="toggle"][class*="enabled"]',all:!0},{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=white]'}]},{name:"dndbeyond",vendorUrl:"https://www.dndbeyond.com/",runContext:{urlPattern:"^https://(www\\.)?dndbeyond\\.com/"},prehideSelectors:["[id^=cookie-consent-banner]"],detectCmp:[{exists:"[id^=cookie-consent-banner]"}],detectPopup:[{visible:"[id^=cookie-consent-banner]"}],optIn:[{waitForThenClick:"#cookie-consent-granted"}],optOut:[{waitForThenClick:"#cookie-consent-denied"}],test:[{eval:"EVAL_DNDBEYOND_TEST"}]},{name:"Drupal",detectCmp:[{exists:"#drupalorg-crosssite-gdpr"}],detectPopup:[{visible:"#drupalorg-crosssite-gdpr"}],optOut:[{click:".no"}],optIn:[{click:".yes"}]},{name:"WP DSGVO Tools",link:"https://wordpress.org/plugins/shapepress-dsgvo/",prehideSelectors:[".sp-dsgvo"],cosmetic:!0,detectCmp:[{exists:".sp-dsgvo.sp-dsgvo-popup-overlay"}],detectPopup:[{visible:".sp-dsgvo.sp-dsgvo-popup-overlay",check:"any"}],optIn:[{click:".sp-dsgvo-privacy-btn-accept-all",all:!0}],optOut:[{hide:".sp-dsgvo.sp-dsgvo-popup-overlay"}],test:[{eval:"EVAL_DSGVO_0"}]},{name:"dunelm.com",prehideSelectors:["div[data-testid=cookie-consent-modal-backdrop]"],detectCmp:[{exists:"div[data-testid=cookie-consent-message-contents]"}],detectPopup:[{visible:"div[data-testid=cookie-consent-message-contents]"}],optIn:[{click:'[data-testid="cookie-consent-allow-all"]'}],optOut:[{click:"button[data-testid=cookie-consent-adjust-settings]"},{click:"button[data-testid=cookie-consent-preferences-save]"}],test:[{eval:"EVAL_DUNELM_0"}]},{name:"ecosia",vendorUrl:"https://www.ecosia.org/",runContext:{urlPattern:"^https://www\\.ecosia\\.org/"},prehideSelectors:[".cookie-wrapper"],detectCmp:[{exists:".cookie-wrapper > .cookie-notice"}],detectPopup:[{visible:".cookie-wrapper > .cookie-notice"}],optIn:[{waitForThenClick:"[data-test-id=cookie-notice-accept]"}],optOut:[{waitForThenClick:"[data-test-id=cookie-notice-reject]"}]},{name:"etsy",prehideSelectors:["#gdpr-single-choice-overlay","#gdpr-privacy-settings"],detectCmp:[{exists:"#gdpr-single-choice-overlay"}],detectPopup:[{visible:"#gdpr-single-choice-overlay"}],optOut:[{click:"button[data-gdpr-open-full-settings]"},{waitForVisible:".gdpr-overlay-body input",timeout:3e3},{wait:1e3},{eval:"EVAL_ETSY_0"},{eval:"EVAL_ETSY_1"}],optIn:[{click:"button[data-gdpr-single-choice-accept]"}]},{name:"eu-cookie-compliance-banner",detectCmp:[{exists:"body.eu-cookie-compliance-popup-open"}],detectPopup:[{exists:"body.eu-cookie-compliance-popup-open"}],optIn:[{click:".agree-button"}],optOut:[{if:{visible:".decline-button,.eu-cookie-compliance-save-preferences-button"},then:[{click:".decline-button,.eu-cookie-compliance-save-preferences-button"}]},{hide:".eu-cookie-compliance-banner-info, #sliding-popup"}],test:[{eval:"EVAL_EU_COOKIE_COMPLIANCE_0"}]},{name:"EU Cookie Law",prehideSelectors:[".pea_cook_wrapper,.pea_cook_more_info_popover"],cosmetic:!0,detectCmp:[{exists:".pea_cook_wrapper"}],detectPopup:[{wait:500},{visible:".pea_cook_wrapper"}],optIn:[{click:"#pea_cook_btn"}],optOut:[{hide:".pea_cook_wrapper"}],test:[{eval:"EVAL_EU_COOKIE_LAW_0"}]},{name:"europa-eu",vendorUrl:"https://ec.europa.eu/",runContext:{urlPattern:"^https://[^/]*europa\\.eu/"},prehideSelectors:["#cookie-consent-banner"],detectCmp:[{exists:".cck-container"}],detectPopup:[{visible:".cck-container"}],optIn:[{waitForThenClick:'.cck-actions-button[href="#accept"]'}],optOut:[{waitForThenClick:'.cck-actions-button[href="#refuse"]',hide:".cck-container"}]},{name:"EZoic",prehideSelectors:["#ez-cookie-dialog-wrapper"],detectCmp:[{exists:"#ez-cookie-dialog-wrapper"}],detectPopup:[{visible:"#ez-cookie-dialog-wrapper"}],optIn:[{click:"#ez-accept-all",optional:!0},{eval:"EVAL_EZOIC_0",optional:!0}],optOut:[{wait:500},{click:"#ez-manage-settings"},{waitFor:"#ez-cookie-dialog input[type=checkbox]"},{click:"#ez-cookie-dialog input[type=checkbox]:checked",all:!0},{click:"#ez-save-settings"}],test:[{eval:"EVAL_EZOIC_1"}]},{name:"facebook",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?facebook\\.com/"},prehideSelectors:['div[data-testid="cookie-policy-manage-dialog"]'],detectCmp:[{exists:'div[data-testid="cookie-policy-manage-dialog"]'}],detectPopup:[{visible:'div[data-testid="cookie-policy-manage-dialog"]'}],optIn:[{waitForThenClick:'button[data-cookiebanner="accept_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}],optOut:[{waitForThenClick:'button[data-cookiebanner="accept_only_essential_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}]},{name:"fides",vendorUrl:"https://github.com/ethyca/fides",prehideSelectors:["#fides-overlay"],detectCmp:[{exists:"#fides-overlay #fides-banner"}],detectPopup:[{visible:"#fides-overlay #fides-banner"}],optIn:[{waitForThenClick:'#fides-banner [data-testid="Accept all-btn"]'}],optOut:[{waitForThenClick:'#fides-banner [data-testid="Reject all-btn"]'}]},{name:"funding-choices",prehideSelectors:[".fc-consent-root,.fc-dialog-container,.fc-dialog-overlay,.fc-dialog-content"],detectCmp:[{exists:".fc-consent-root"}],detectPopup:[{exists:".fc-dialog-container"}],optOut:[{click:".fc-cta-do-not-consent,.fc-cta-manage-options"},{click:".fc-preference-consent:checked,.fc-preference-legitimate-interest:checked",all:!0,optional:!0},{click:".fc-confirm-choices",optional:!0}],optIn:[{click:".fc-cta-consent"}]},{name:"geeks-for-geeks",runContext:{urlPattern:"^https://www\\.geeksforgeeks\\.org/"},cosmetic:!0,prehideSelectors:[".cookie-consent"],detectCmp:[{exists:".cookie-consent"}],detectPopup:[{visible:".cookie-consent"}],optIn:[{click:".cookie-consent button.consent-btn"}],optOut:[{hide:".cookie-consent"}]},{name:"generic-cosmetic",cosmetic:!0,prehideSelectors:["#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"],detectCmp:[{exists:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],detectPopup:[{visible:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],optIn:[],optOut:[{hide:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}]},{name:"google-consent-standalone",prehideSelectors:[],detectCmp:[{exists:'a[href^="https://policies.google.com/technologies/cookies"'},{exists:'form[action^="https://consent.google."][action$=".com/save"]'}],detectPopup:[{visible:'a[href^="https://policies.google.com/technologies/cookies"'}],optIn:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=false]) button'}],optOut:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=true]) button'}]},{name:"google.com",prehideSelectors:[".HTjtHe#xe7COe"],detectCmp:[{exists:".HTjtHe#xe7COe"},{exists:'.HTjtHe#xe7COe a[href^="https://policies.google.com/technologies/cookies"]'}],detectPopup:[{visible:".HTjtHe#xe7COe button#W0wltc"}],optIn:[{waitForThenClick:".HTjtHe#xe7COe button#L2AGLb"}],optOut:[{waitForThenClick:".HTjtHe#xe7COe button#W0wltc"}],test:[{eval:"EVAL_GOOGLE_0"}]},{name:"gov.uk",detectCmp:[{exists:"#global-cookie-message"}],detectPopup:[{exists:"#global-cookie-message"}],optIn:[{click:"button[data-accept-cookies=true]"}],optOut:[{click:"button[data-reject-cookies=true],#reject-cookies"},{click:"button[data-hide-cookie-banner=true],#hide-cookie-decision"}]},{name:"hashicorp",vendorUrl:"https://hashicorp.com/",runContext:{urlPattern:"^https://[^.]*\\.hashicorp\\.com/"},prehideSelectors:["[data-testid=consent-banner]"],detectCmp:[{exists:"[data-testid=consent-banner]"}],detectPopup:[{visible:"[data-testid=consent-banner]"}],optIn:[{waitForThenClick:"[data-testid=accept]"}],optOut:[{waitForThenClick:"[data-testid=manage-preferences]"},{waitForThenClick:"[data-testid=consent-mgr-dialog] [data-ga-button=save-preferences]"}]},{name:"healthline-media",prehideSelectors:["#modal-host > div.no-hash > div.window-wrapper"],detectCmp:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],detectPopup:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],optIn:[{click:"#modal-host > div.no-hash > div.window-wrapper > div:last-child button"}],optOut:[{if:{exists:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'},then:[{click:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'}],else:[{waitForVisible:"div#__next"},{click:"#__next div:nth-child(1) > button:first-child"}]}]},{name:"hema",prehideSelectors:[".cookie-modal"],detectCmp:[{visible:".cookie-modal .cookie-accept-btn"}],detectPopup:[{visible:".cookie-modal .cookie-accept-btn"}],optIn:[{waitForThenClick:".cookie-modal .cookie-accept-btn"}],optOut:[{waitForThenClick:".cookie-modal .js-cookie-reject-btn"}],test:[{eval:"EVAL_HEMA_TEST_0"}]},{name:"hetzner.com",runContext:{urlPattern:"^https://www\\.hetzner\\.com/"},prehideSelectors:["#CookieConsent"],detectCmp:[{exists:"#CookieConsent"}],detectPopup:[{visible:"#CookieConsent"}],optIn:[{click:"#CookieConsentGiven"}],optOut:[{click:"#CookieConsentDeclined"}]},{name:"hl.co.uk",prehideSelectors:[".cookieModalContent","#cookie-banner-overlay"],detectCmp:[{exists:"#cookie-banner-overlay"}],detectPopup:[{exists:"#cookie-banner-overlay"}],optIn:[{click:"#acceptCookieButton"}],optOut:[{click:"#manageCookie"},{hide:".cookieSettingsModal"},{waitFor:"#AOCookieToggle"},{click:"#AOCookieToggle[aria-pressed=true]",optional:!0},{waitFor:"#TPCookieToggle"},{click:"#TPCookieToggle[aria-pressed=true]",optional:!0},{click:"#updateCookieButton"}]},{name:"hu-manity",vendorUrl:"https://hu-manity.co/",prehideSelectors:["#hu.hu-wrapper"],detectCmp:[{exists:"#hu.hu-visible"}],detectPopup:[{visible:"#hu.hu-visible"}],optIn:[{waitForThenClick:"[data-hu-action=cookies-notice-consent-choices-3]"},{waitForThenClick:"#hu-cookies-save"}],optOut:[{waitForThenClick:"#hu-cookies-save"}]},{name:"hubspot",detectCmp:[{exists:"#hs-eu-cookie-confirmation"}],detectPopup:[{visible:"#hs-eu-cookie-confirmation"}],optIn:[{click:"#hs-eu-confirmation-button"}],optOut:[{click:"#hs-eu-decline-button"}]},{name:"indeed.com",cosmetic:!0,prehideSelectors:["#CookiePrivacyNotice"],detectCmp:[{exists:"#CookiePrivacyNotice"}],detectPopup:[{visible:"#CookiePrivacyNotice"}],optIn:[{click:"#CookiePrivacyNotice button[data-gnav-element-name=CookiePrivacyNoticeOk]"}],optOut:[{hide:"#CookiePrivacyNotice"}]},{name:"ing.de",runContext:{urlPattern:"^https://www\\.ing\\.de/"},cosmetic:!0,prehideSelectors:['div[slot="backdrop"]'],detectCmp:[{exists:'[data-tag-name="ing-cc-dialog-frame"]'}],detectPopup:[{visible:'[data-tag-name="ing-cc-dialog-frame"]'}],optIn:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="accept"]']}],optOut:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="more"]']}]},{name:"instagram",vendorUrl:"https://instagram.com",runContext:{urlPattern:"^https://www\\.instagram\\.com/"},prehideSelectors:[".x78zum5.xdt5ytf.xg6iff7.x1n2onr6"],detectCmp:[{exists:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],detectPopup:[{visible:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],optIn:[{waitForThenClick:"._a9--._a9_0"}],optOut:[{waitForThenClick:"._a9--._a9_1"},{wait:2e3}]},{name:"ionos.de",prehideSelectors:[".privacy-consent--backdrop",".privacy-consent--modal"],detectCmp:[{exists:".privacy-consent--modal"}],detectPopup:[{visible:".privacy-consent--modal"}],optIn:[{click:"#selectAll"}],optOut:[{click:".footer-config-link"},{click:"#confirmSelection"}]},{name:"itopvpn.com",cosmetic:!0,prehideSelectors:[".pop-cookie"],detectCmp:[{exists:".pop-cookie"}],detectPopup:[{exists:".pop-cookie"}],optIn:[{click:"#_pcookie"}],optOut:[{hide:".pop-cookie"}]},{name:"iubenda",prehideSelectors:["#iubenda-cs-banner"],detectCmp:[{exists:"#iubenda-cs-banner"}],detectPopup:[{visible:".iubenda-cs-accept-btn"}],optIn:[{click:".iubenda-cs-accept-btn"}],optOut:[{click:".iubenda-cs-customize-btn"},{eval:"EVAL_IUBENDA_0"},{click:"#iubFooterBtn"}],test:[{eval:"EVAL_IUBENDA_1"}]},{name:"iWink",prehideSelectors:["body.cookies-request #cookie-bar"],detectCmp:[{exists:"body.cookies-request #cookie-bar"}],detectPopup:[{visible:"body.cookies-request #cookie-bar"}],optIn:[{waitForThenClick:"body.cookies-request #cookie-bar .allow-cookies"}],optOut:[{waitForThenClick:"body.cookies-request #cookie-bar .disallow-cookies"}],test:[{eval:"EVAL_IWINK_TEST"}]},{name:"jdsports",vendorUrl:"https://www.jdsports.co.uk/",runContext:{urlPattern:"^https://(www|m)\\.jdsports\\."},prehideSelectors:[".miniConsent,#PrivacyPolicyBanner"],detectCmp:[{exists:".miniConsent,#PrivacyPolicyBanner"}],detectPopup:[{visible:".miniConsent,#PrivacyPolicyBanner"}],optIn:[{waitForThenClick:".miniConsent .accept-all-cookies"}],optOut:[{if:{exists:"#PrivacyPolicyBanner"},then:[{hide:"#PrivacyPolicyBanner"}],else:[{waitForThenClick:"#cookie-settings"},{waitForThenClick:"#reject-all-cookies"}]}]},{name:"johnlewis.com",prehideSelectors:["div[class^=pecr-cookie-banner-]"],detectCmp:[{exists:"div[class^=pecr-cookie-banner-]"}],detectPopup:[{exists:"div[class^=pecr-cookie-banner-]"}],optOut:[{click:"button[data-test^=manage-cookies]"},{wait:"500"},{click:"label[data-test^=toggle][class*=checked]:not([class*=disabled])",all:!0,optional:!0},{click:"button[data-test=save-preferences]"}],optIn:[{click:"button[data-test=allow-all]"}]},{name:"jquery.cookieBar",vendorUrl:"https://github.com/kovarp/jquery.cookieBar",prehideSelectors:[".cookie-bar"],cosmetic:!0,detectCmp:[{exists:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons"}],detectPopup:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"any"}],optIn:[{click:".cookie-bar .cookie-bar__btn"}],optOut:[{hide:".cookie-bar"}],test:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"none"},{eval:"EVAL_JQUERY_COOKIEBAR_0"}]},{name:"justwatch.com",prehideSelectors:[".consent-banner"],detectCmp:[{exists:".consent-banner .consent-banner__actions"}],detectPopup:[{visible:".consent-banner .consent-banner__actions"}],optIn:[{click:".consent-banner__actions button.basic-button.primary"}],optOut:[{click:".consent-banner__actions button.basic-button.secondary"},{waitForThenClick:".consent-modal__footer button.basic-button.secondary"},{waitForThenClick:".consent-modal ion-content > div > a:nth-child(9)"},{click:"label.consent-switch input[type=checkbox]:checked",all:!0,optional:!0},{waitForVisible:".consent-modal__footer button.basic-button.primary"},{click:".consent-modal__footer button.basic-button.primary"}]},{name:"ketch",vendorUrl:"https://www.ketch.com",runContext:{frame:!1,main:!0},intermediate:!1,prehideSelectors:["#lanyard_root div[role='dialog']"],detectCmp:[{exists:"#lanyard_root div[role='dialog']"}],detectPopup:[{visible:"#lanyard_root div[role='dialog']"}],optIn:[{if:{exists:"#lanyard_root button[class='confirmButton']"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"},{click:"#lanyard_root button[class='confirmButton']"}],else:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"}]}],optOut:[{if:{exists:"#lanyard_root [aria-describedby=banner-description]"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > button[class*=secondaryButton]",comment:"can be either settings or reject button"}]},{waitFor:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]",timeout:1e3,optional:!0},{if:{exists:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]"},then:[{waitForThenClick:"#lanyard_root button[class*=rejectButton]"},{click:"#lanyard_root button[class*=confirmButton],#lanyard_root div[class*=actions_] > button:nth-child(1)"}]}]},{name:"kleinanzeigen-de",runContext:{urlPattern:"^https?://(www\\.)?kleinanzeigen\\.de"},prehideSelectors:["#gdpr-banner-container"],detectCmp:[{any:[{exists:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{exists:"#ConsentManagementPage"}]}],detectPopup:[{any:[{visible:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{visible:"#ConsentManagementPage"}]}],optIn:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-accept]"}],else:[{click:"#ConsentManagementPage .Button-primary"}]}],optOut:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"}],else:[{click:"#ConsentManagementPage .Button-secondary"}]}]},{name:"lightbox",prehideSelectors:[".darken-layer.open,.lightbox.lightbox--cookie-consent"],detectCmp:[{exists:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],detectPopup:[{visible:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],optOut:[{click:".cookie-consent__footer > button[type='submit']:not([data-button='selectAll'])"}],optIn:[{click:".cookie-consent__footer > button[type='submit'][data-button='selectAll']"}]},{name:"lineagrafica",vendorUrl:"https://addons.prestashop.com/en/legal/8734-eu-cookie-law-gdpr-banner-blocker.html",cosmetic:!0,prehideSelectors:["#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"],detectCmp:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],detectPopup:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],optIn:[{waitForThenClick:"#lgcookieslaw_accept"}],optOut:[{hide:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}]},{name:"linkedin.com",prehideSelectors:[".artdeco-global-alert[type=COOKIE_CONSENT]"],detectCmp:[{exists:".artdeco-global-alert[type=COOKIE_CONSENT]"}],detectPopup:[{visible:".artdeco-global-alert[type=COOKIE_CONSENT]"}],optIn:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"}],optOut:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"}],test:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT]",check:"none"}]},{name:"livejasmin",vendorUrl:"https://www.livejasmin.com/",runContext:{urlPattern:"^https://(m|www)\\.livejasmin\\.com/"},prehideSelectors:["#consent_modal"],detectCmp:[{exists:"#consent_modal"}],detectPopup:[{visible:"#consent_modal"}],optIn:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:first-of-type"}],optOut:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:nth-of-type(2)"},{waitForVisible:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent]"},{click:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] input[data-testid=PrivacyPreferenceCenterWithConsentCookieSwitch]:checked",optional:!0,all:!0},{waitForThenClick:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] button[data-testid=ButtonStyledButton]:last-child"}]},{name:"macpaw.com",cosmetic:!0,prehideSelectors:['div[data-banner="cookies"]'],detectCmp:[{exists:'div[data-banner="cookies"]'}],detectPopup:[{exists:'div[data-banner="cookies"]'}],optIn:[{click:'button[data-banner-close="cookies"]'}],optOut:[{hide:'div[data-banner="cookies"]'}]},{name:"marksandspencer.com",cosmetic:!0,detectCmp:[{exists:".navigation-cookiebbanner"}],detectPopup:[{visible:".navigation-cookiebbanner"}],optOut:[{hide:".navigation-cookiebbanner"}],optIn:[{click:".navigation-cookiebbanner__submit"}]},{name:"mediamarkt.de",prehideSelectors:["div[aria-labelledby=pwa-consent-layer-title]","div[class^=StyledConsentLayerWrapper-]"],detectCmp:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],detectPopup:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],optOut:[{click:"button[data-test^=pwa-consent-layer-deny-all]"}],optIn:[{click:"button[data-test^=pwa-consent-layer-accept-all"}]},{name:"Mediavine",prehideSelectors:['[data-name="mediavine-gdpr-cmp"]'],detectCmp:[{exists:'[data-name="mediavine-gdpr-cmp"]'}],detectPopup:[{wait:500},{visible:'[data-name="mediavine-gdpr-cmp"]'}],optIn:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [format="primary"]'}],optOut:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [data-view="manageSettings"]'},{waitFor:'[data-name="mediavine-gdpr-cmp"] input[type=checkbox]'},{eval:"EVAL_MEDIAVINE_0",optional:!0},{click:'[data-name="mediavine-gdpr-cmp"] [format="secondary"]'}]},{name:"microsoft.com",prehideSelectors:["#wcpConsentBannerCtrl"],detectCmp:[{exists:"#wcpConsentBannerCtrl"}],detectPopup:[{exists:"#wcpConsentBannerCtrl"}],optOut:[{eval:"EVAL_MICROSOFT_0"}],optIn:[{eval:"EVAL_MICROSOFT_1"}],test:[{eval:"EVAL_MICROSOFT_2"}]},{name:"midway-usa",runContext:{urlPattern:"^https://www\\.midwayusa\\.com/"},cosmetic:!0,prehideSelectors:["#cookie-container"],detectCmp:[{exists:['div[aria-label="Cookie Policy Banner"]']}],detectPopup:[{visible:"#cookie-container"}],optIn:[{click:"button#cookie-btn"}],optOut:[{hide:'div[aria-label="Cookie Policy Banner"]'}]},{name:"moneysavingexpert.com",detectCmp:[{exists:"dialog[data-testid=accept-our-cookies-dialog]"}],detectPopup:[{visible:"dialog[data-testid=accept-our-cookies-dialog]"}],optIn:[{click:"#banner-accept"}],optOut:[{click:"#banner-manage"},{click:"#pc-confirm"}]},{name:"monzo.com",prehideSelectors:[".cookie-alert, cookie-alert__content"],detectCmp:[{exists:'div.cookie-alert[role="dialog"]'},{exists:'a[href*="monzo"]'}],detectPopup:[{visible:".cookie-alert__content"}],optIn:[{click:".js-accept-cookie-policy"}],optOut:[{click:".js-decline-cookie-policy"}]},{name:"Moove",prehideSelectors:["#moove_gdpr_cookie_info_bar"],detectCmp:[{exists:"#moove_gdpr_cookie_info_bar"}],detectPopup:[{visible:"#moove_gdpr_cookie_info_bar"}],optIn:[{waitForThenClick:".moove-gdpr-infobar-allow-all"}],optOut:[{if:{exists:"#moove_gdpr_cookie_info_bar .change-settings-button"},then:[{click:"#moove_gdpr_cookie_info_bar .change-settings-button"},{waitForVisible:"#moove_gdpr_cookie_modal"},{eval:"EVAL_MOOVE_0"},{click:".moove-gdpr-modal-save-settings"}],else:[{hide:"#moove_gdpr_cookie_info_bar"}]}],test:[{visible:"#moove_gdpr_cookie_info_bar",check:"none"}]},{name:"national-lottery.co.uk",detectCmp:[{exists:".cuk_cookie_consent"}],detectPopup:[{visible:".cuk_cookie_consent",check:"any"}],optOut:[{click:".cuk_cookie_consent_manage_pref"},{click:".cuk_cookie_consent_save_pref"},{click:".cuk_cookie_consent_close"}],optIn:[{click:".cuk_cookie_consent_accept_all"}]},{name:"nba.com",runContext:{urlPattern:"^https://(www\\.)?nba.com/"},cosmetic:!0,prehideSelectors:["#onetrust-banner-sdk"],detectCmp:[{exists:"#onetrust-banner-sdk"}],detectPopup:[{visible:"#onetrust-banner-sdk"}],optIn:[{click:"#onetrust-accept-btn-handler"}],optOut:[{hide:"#onetrust-banner-sdk"}]},{name:"netflix.de",detectCmp:[{exists:"#cookie-disclosure"}],detectPopup:[{visible:".cookie-disclosure-message",check:"any"}],optIn:[{click:".btn-accept"}],optOut:[{hide:"#cookie-disclosure"},{click:".btn-reject"}]},{name:"nhs.uk",prehideSelectors:["#nhsuk-cookie-banner"],detectCmp:[{exists:"#nhsuk-cookie-banner"}],detectPopup:[{exists:"#nhsuk-cookie-banner"}],optOut:[{click:"#nhsuk-cookie-banner__link_accept"}],optIn:[{click:"#nhsuk-cookie-banner__link_accept_analytics"}]},{name:"notice-cookie",prehideSelectors:[".button--notice"],cosmetic:!0,detectCmp:[{exists:".notice--cookie"}],detectPopup:[{visible:".notice--cookie"}],optIn:[{click:".button--notice"}],optOut:[{hide:".notice--cookie"}]},{name:"nrk.no",cosmetic:!0,prehideSelectors:[".nrk-masthead__info-banner--cookie"],detectCmp:[{exists:".nrk-masthead__info-banner--cookie"}],detectPopup:[{exists:".nrk-masthead__info-banner--cookie"}],optIn:[{click:"div.nrk-masthead__info-banner--cookie button > span:has(+ svg.nrk-close)"}],optOut:[{hide:".nrk-masthead__info-banner--cookie"}]},{name:"obi.de",prehideSelectors:[".disc-cp--active"],detectCmp:[{exists:".disc-cp-modal__modal"}],detectPopup:[{visible:".disc-cp-modal__modal"}],optIn:[{click:".js-disc-cp-accept-all"}],optOut:[{click:".js-disc-cp-deny-all"}]},{name:"om",vendorUrl:"https://olli-machts.de/en/extension/cookie-manager",prehideSelectors:[".tx-om-cookie-consent"],detectCmp:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],detectPopup:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],optIn:[{waitForThenClick:"[data-omcookie-panel-save=all]"}],optOut:[{if:{exists:"[data-omcookie-panel-save=min]"},then:[{waitForThenClick:"[data-omcookie-panel-save=min]"}],else:[{click:"input[data-omcookie-panel-grp]:checked:not(:disabled)",all:!0,optional:!0},{waitForThenClick:"[data-omcookie-panel-save=save]"}]}]},{name:"onlyFans.com",prehideSelectors:["div.b-cookies-informer"],detectCmp:[{exists:"div.b-cookies-informer"}],detectPopup:[{exists:"div.b-cookies-informer"}],optIn:[{click:"div.b-cookies-informer__nav > button:nth-child(2)"}],optOut:[{click:"div.b-cookies-informer__nav > button:nth-child(1)"},{click:'div.b-cookies-informer__switchers > div:nth-child(2) > div[at-attr="checkbox"] > span.b-input-radio__container > input[type="checkbox"]'},{click:"div.b-cookies-informer__nav > button"}]},{name:"openli",vendorUrl:"https://openli.com",prehideSelectors:[".legalmonster-cleanslate"],detectCmp:[{exists:".legalmonster-cleanslate"}],detectPopup:[{visible:".legalmonster-cleanslate #lm-cookie-wall-container",check:"any"}],optIn:[{waitForThenClick:"#lm-accept-all"}],optOut:[{waitForThenClick:"#lm-accept-necessary"}]},{name:"opera.com",vendorUrl:"https://unknown",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:"#cookie-consent .manage-cookies__btn"}],detectPopup:[{visible:"#cookie-consent .cookie-basic-consent__btn"}],optIn:[{waitForThenClick:"#cookie-consent .cookie-basic-consent__btn"}],optOut:[{waitForThenClick:"#cookie-consent .manage-cookies__btn"},{waitForThenClick:"#cookie-consent .active.marketing_option_switch.cookie-consent__switch",all:!0},{waitForThenClick:"#cookie-consent .cookie-selection__btn"}],test:[{eval:"EVAL_OPERA_0"}]},{name:"osano",prehideSelectors:[".osano-cm-window,.osano-cm-dialog"],detectCmp:[{exists:".osano-cm-window"}],detectPopup:[{visible:".osano-cm-dialog"}],optIn:[{click:".osano-cm-accept-all",optional:!0}],optOut:[{waitForThenClick:".osano-cm-denyAll"}]},{name:"otto.de",prehideSelectors:[".cookieBanner--visibility"],detectCmp:[{exists:".cookieBanner--visibility"}],detectPopup:[{visible:".cookieBanner__wrapper"}],optIn:[{click:".js_cookieBannerPermissionButton"}],optOut:[{click:".js_cookieBannerProhibitionButton"}]},{name:"ourworldindata",vendorUrl:"https://ourworldindata.org/",runContext:{urlPattern:"^https://ourworldindata\\.org/"},prehideSelectors:[".cookie-manager"],detectCmp:[{exists:".cookie-manager"}],detectPopup:[{visible:".cookie-manager .cookie-notice.open"}],optIn:[{waitForThenClick:".cookie-notice [data-test=accept]"}],optOut:[{waitForThenClick:".cookie-notice [data-test=reject]"}]},{name:"pabcogypsum",vendorUrl:"https://unknown",prehideSelectors:[".js-cookie-notice:has(#cookie_settings-form)"],detectCmp:[{exists:".js-cookie-notice #cookie_settings-form"}],detectPopup:[{visible:".js-cookie-notice #cookie_settings-form"}],optIn:[{waitForThenClick:".js-cookie-notice button[value=allow]"}],optOut:[{waitForThenClick:".js-cookie-notice button[value=disable]"}]},{name:"paypal-us",prehideSelectors:["#ccpaCookieContent_wrapper, article.ppvx_modal--overpanel"],detectCmp:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],detectPopup:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"a#manageCookiesLink"},then:[{click:"a#manageCookiesLink"}],else:[{waitForVisible:".privacy-sheet-content #formContent"},{click:"#formContent .cookiepref-11m2iee-checkbox_base input:checked",all:!0,optional:!0},{click:".confirmCookie #submitCookiesBtn"}]}]},{name:"paypal.com",prehideSelectors:["#gdprCookieBanner"],detectCmp:[{exists:"#gdprCookieBanner"}],detectPopup:[{visible:"#gdprCookieContent_wrapper"}],optIn:[{click:"#acceptAllButton"}],optOut:[{wait:200},{click:".gdprCookieBanner_decline-button"}],test:[{wait:500},{eval:"EVAL_PAYPAL_0"}]},{name:"pinetools.com",cosmetic:!0,prehideSelectors:["#aviso_cookies"],detectCmp:[{exists:"#aviso_cookies"}],detectPopup:[{exists:".lang_en #aviso_cookies"}],optIn:[{click:"#aviso_cookies .a_boton_cerrar"}],optOut:[{hide:"#aviso_cookies"}]},{name:"pmc",cosmetic:!0,prehideSelectors:["#pmc-pp-tou--notice"],detectCmp:[{exists:"#pmc-pp-tou--notice"}],detectPopup:[{visible:"#pmc-pp-tou--notice"}],optIn:[{click:"span.pmc-pp-tou--notice-close-btn"}],optOut:[{hide:"#pmc-pp-tou--notice"}]},{name:"pornhub.com",runContext:{urlPattern:"^https://(www\\.)?pornhub\\.com/"},cosmetic:!0,prehideSelectors:[".cookiesBanner"],detectCmp:[{exists:".cookiesBanner"}],detectPopup:[{visible:".cookiesBanner"}],optIn:[{click:".cookiesBanner .okButton"}],optOut:[{hide:".cookiesBanner"}]},{name:"pornpics.com",cosmetic:!0,prehideSelectors:["#cookie-contract"],detectCmp:[{exists:"#cookie-contract"}],detectPopup:[{visible:"#cookie-contract"}],optIn:[{click:"#cookie-contract .icon-cross"}],optOut:[{hide:"#cookie-contract"}]},{name:"PrimeBox CookieBar",prehideSelectors:["#cookie-bar"],detectCmp:[{exists:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy"}],detectPopup:[{visible:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy",check:"any"}],optIn:[{waitForThenClick:"#cookie-bar .cb-enable"}],optOut:[{click:"#cookie-bar .cb-disable",optional:!0},{hide:"#cookie-bar"}],test:[{eval:"EVAL_PRIMEBOX_0"}]},{name:"privacymanager.io",prehideSelectors:["#gdpr-consent-tool-wrapper",'iframe[src^="https://cmp-consent-tool.privacymanager.io"]'],runContext:{urlPattern:"^https://cmp-consent-tool\\.privacymanager\\.io/",main:!1,frame:!0},detectCmp:[{exists:"button#save"}],detectPopup:[{visible:"button#save"}],optIn:[{click:"button#save"}],optOut:[{if:{exists:"#denyAll"},then:[{click:"#denyAll"},{waitForThenClick:".okButton"}],else:[{waitForThenClick:"#manageSettings"},{waitFor:".purposes-overview-list"},{waitFor:"button#saveAndExit"},{click:"span[role=checkbox][aria-checked=true]",all:!0,optional:!0},{click:"button#saveAndExit"}]}]},{name:"productz.com",vendorUrl:"https://productz.com/",runContext:{urlPattern:"^https://productz\\.com/"},prehideSelectors:[],detectCmp:[{exists:".c-modal.is-active"}],detectPopup:[{visible:".c-modal.is-active"}],optIn:[{waitForThenClick:".c-modal.is-active .is-accept"}],optOut:[{waitForThenClick:".c-modal.is-active .is-dismiss"}]},{name:"pubtech",prehideSelectors:["#pubtech-cmp"],detectCmp:[{exists:"#pubtech-cmp"}],detectPopup:[{visible:"#pubtech-cmp #pt-actions"}],optIn:[{if:{exists:"#pt-accept-all"},then:[{click:"#pubtech-cmp #pt-actions #pt-accept-all"}],else:[{click:"#pubtech-cmp #pt-actions button:nth-of-type(2)"}]}],optOut:[{click:"#pubtech-cmp #pt-close"}],test:[{eval:"EVAL_PUBTECH_0"}]},{name:"quantcast",prehideSelectors:["#qc-cmp2-main,#qc-cmp2-container"],detectCmp:[{exists:"#qc-cmp2-container"}],detectPopup:[{visible:"#qc-cmp2-ui"}],optOut:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]'},{waitFor:"#qc-cmp2-ui"},{click:'.qc-cmp2-toggle-switch > button[aria-checked="true"]',all:!0,optional:!0},{click:'.qc-cmp2-main button[aria-label="REJECT ALL"]',optional:!0},{waitForThenClick:'.qc-cmp2-main button[aria-label="SAVE & EXIT"],.qc-cmp2-buttons-desktop > button[mode="primary"]',timeout:5e3}],optIn:[{click:'.qc-cmp2-summary-buttons > button[mode="primary"]'}]},{name:"reddit.com",runContext:{urlPattern:"^https://www\\.reddit\\.com/"},prehideSelectors:["[bundlename=reddit_cookie_banner]"],detectCmp:[{exists:"reddit-cookie-banner"}],detectPopup:[{visible:"reddit-cookie-banner"}],optIn:[{waitForThenClick:["reddit-cookie-banner","#accept-all-cookies-button > button"]}],optOut:[{waitForThenClick:["reddit-cookie-banner","#reject-nonessential-cookies-button > button"]}],test:[{eval:"EVAL_REDDIT_0"}]},{name:"rog-forum.asus.com",runContext:{urlPattern:"^https://rog-forum\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{click:'div.cookie-btn-box > div[aria-label="Accept"]'}],optOut:[{click:'div.cookie-btn-box > div[aria-label="Reject"]'},{waitForThenClick:'.cookie-policy-lightbox-bottom > div[aria-label="Save Settings"]'}]},{name:"roofingmegastore.co.uk",runContext:{urlPattern:"^https://(www\\.)?roofingmegastore\\.co\\.uk"},prehideSelectors:["#m-cookienotice"],detectCmp:[{exists:"#m-cookienotice"}],detectPopup:[{visible:"#m-cookienotice"}],optIn:[{click:"#accept-cookies"}],optOut:[{click:"#manage-cookies"},{waitForThenClick:"#accept-selected"}]},{name:"samsung.com",runContext:{urlPattern:"^https://www\\.samsung\\.com/"},cosmetic:!0,prehideSelectors:["div.cookie-bar"],detectCmp:[{exists:"div.cookie-bar"}],detectPopup:[{visible:"div.cookie-bar"}],optIn:[{click:"div.cookie-bar__manage > a"}],optOut:[{hide:"div.cookie-bar"}]},{name:"setapp.com",vendorUrl:"https://setapp.com/",cosmetic:!0,runContext:{urlPattern:"^https://setapp\\.com/"},prehideSelectors:[],detectCmp:[{exists:".cookie-banner.js-cookie-banner"}],detectPopup:[{visible:".cookie-banner.js-cookie-banner"}],optIn:[{waitForThenClick:".cookie-banner.js-cookie-banner button"}],optOut:[{hide:".cookie-banner.js-cookie-banner"}]},{name:"sibbo",prehideSelectors:["sibbo-cmp-layout"],detectCmp:[{exists:"sibbo-cmp-layout"}],detectPopup:[{visible:"sibbo-cmp-layout"}],optIn:[{click:"sibbo-cmp-layout [data-accept-all]"}],optOut:[{click:'.sibbo-panel__aside__buttons a[data-nav="purposes"]'},{click:'.sibbo-panel__main__header__actions a[data-focusable="reject-all"]'},{if:{exists:"[data-view=purposes] .sibbo-panel__main__footer__actions [data-save-and-exit]"},then:[],else:[{waitFor:'.sibbo-panel__main__footer__actions a[data-focusable="next"]:not(.sibbo-cmp-button--disabled)'},{click:'.sibbo-panel__main__footer__actions a[data-focusable="next"]'},{click:'.sibbo-panel__main div[data-view="purposesLegInt"] a[data-focusable="reject-all"]'}]},{waitFor:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"},{click:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"}],test:[{eval:"EVAL_SIBBO_0"}]},{name:"similarweb.com",cosmetic:!0,prehideSelectors:[".app-cookies-notification"],detectCmp:[{exists:".app-cookies-notification"}],detectPopup:[{exists:".app-layout .app-cookies-notification"}],optIn:[{click:"button.app-cookies-notification__dismiss"}],optOut:[{hide:".app-layout .app-cookies-notification"}]},{name:"Sirdata",cosmetic:!1,prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:["#sd-cmp","xpath///span[contains(., 'Do not accept') or contains(., 'Acceptera inte') or contains(., 'No aceptar') or contains(., 'Ikke acceptere') or contains(., 'Nicht akzeptieren') or contains(., 'Не приемам') or contains(., 'Να μην γίνει αποδοχή') or contains(., 'Niet accepteren') or contains(., 'Nepřijímat') or contains(., 'Nie akceptuj') or contains(., 'Nu acceptați') or contains(., 'Não aceitar') or contains(., 'Continuer sans accepter') or contains(., 'Non accettare') or contains(., 'Nem fogad el')]"]}]},{name:"snigel",detectCmp:[{exists:".snigel-cmp-framework"}],detectPopup:[{visible:".snigel-cmp-framework"}],optOut:[{click:"#sn-b-custom"},{click:"#sn-b-save"}],test:[{eval:"EVAL_SNIGEL_0"}],optIn:[{click:".snigel-cmp-framework #accept-choices"}]},{name:"steampowered.com",detectCmp:[{exists:".cookiepreferences_popup"},{visible:".cookiepreferences_popup"}],detectPopup:[{visible:".cookiepreferences_popup"}],optOut:[{click:"#rejectAllButton"}],optIn:[{click:"#acceptAllButton"}],test:[{wait:1e3},{eval:"EVAL_STEAMPOWERED_0"}]},{name:"strato.de",prehideSelectors:["#cookie_initial_modal",".modal-backdrop"],runContext:{urlPattern:"^https://www\\.strato\\.de/"},detectCmp:[{exists:"#cookie_initial_modal"}],detectPopup:[{visible:"#cookie_initial_modal"}],optIn:[{click:"button#jss_consent_all_initial_modal"}],optOut:[{click:"button#jss_open_settings_modal"},{click:"button#jss_consent_checked"}]},{name:"svt.se",vendorUrl:"https://www.svt.se/",runContext:{urlPattern:"^https://www\\.svt\\.se/"},prehideSelectors:["[class*=CookieConsent__root___]"],detectCmp:[{exists:"[class*=CookieConsent__root___]"}],detectPopup:[{visible:"[class*=CookieConsent__modal___]"}],optIn:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=primary]"}],optOut:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=secondary]:nth-child(2)"}],test:[{eval:"EVAL_SVT_TEST"}]},{name:"takealot.com",cosmetic:!0,prehideSelectors:['div[class^="cookies-banner-module_"]'],detectCmp:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],detectPopup:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],optIn:[{click:'button[class*="cookies-banner-module_dismiss-button_"]'}],optOut:[{hide:'div[class^="cookies-banner-module_"]'},{if:{exists:'div[class^="cookies-banner-module_small-cookie-banner_"]'},then:[{eval:"EVAL_TAKEALOT_0"}],else:[]}]},{name:"tarteaucitron.js",prehideSelectors:["#tarteaucitronRoot"],detectCmp:[{exists:"#tarteaucitronRoot"}],detectPopup:[{visible:"#tarteaucitronRoot #tarteaucitronAlertSmall,#tarteaucitronRoot #tarteaucitronAlertBig",check:"any"}],optIn:[{eval:"EVAL_TARTEAUCITRON_1"}],optOut:[{eval:"EVAL_TARTEAUCITRON_0"}],test:[{eval:"EVAL_TARTEAUCITRON_2",comment:"sometimes there are required categories, so we check that at least something is false"}]},{name:"taunton",vendorUrl:"https://www.taunton.com/",prehideSelectors:["#taunton-user-consent__overlay"],detectCmp:[{exists:"#taunton-user-consent__overlay"}],detectPopup:[{exists:"#taunton-user-consent__overlay:not([aria-hidden=true])"}],optIn:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:not(:checked)"},{click:"#taunton-user-consent__toolbar button[type=submit]"}],optOut:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:checked",optional:!0,all:!0},{click:"#taunton-user-consent__toolbar button[type=submit]"}],test:[{eval:"EVAL_TAUNTON_TEST"}]},{name:"Tealium",prehideSelectors:["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal,#consent-layer"],detectCmp:[{exists:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *"},{eval:"EVAL_TEALIUM_0"}],detectPopup:[{visible:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *",check:"any"}],optOut:[{eval:"EVAL_TEALIUM_1"},{eval:"EVAL_TEALIUM_DONOTSELL"},{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal"},{waitForThenClick:"#cm-acceptNone,.js-accept-essential-cookies",timeout:1e3,optional:!0}],optIn:[{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs"},{eval:"EVAL_TEALIUM_2"}],test:[{eval:"EVAL_TEALIUM_3"},{eval:"EVAL_TEALIUM_DONOTSELL_CHECK"},{visible:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs",check:"none"}]},{name:"temu",vendorUrl:"https://temu.com",runContext:{urlPattern:"^https://[^/]*temu\\.com/"},prehideSelectors:["._2d-8vq-W,._1UdBUwni"],detectCmp:[{exists:"._3YCsmIaS"}],detectPopup:[{visible:"._3YCsmIaS"}],optIn:[{waitForThenClick:"._3fKiu5wx._3zN5SumS._3tAK973O.IYOfhWEs.VGNGF1pA"}],optOut:[{waitForThenClick:"._3fKiu5wx._1_XToJBF._3tAK973O.IYOfhWEs.VGNGF1pA"}]},{name:"Termly",prehideSelectors:["#termly-code-snippet-support"],detectCmp:[{exists:"#termly-code-snippet-support"}],detectPopup:[{visible:"#termly-code-snippet-support div"}],optIn:[{waitForThenClick:'[data-tid="banner-accept"]'}],optOut:[{if:{exists:'[data-tid="banner-decline"]'},then:[{click:'[data-tid="banner-decline"]'}],else:[{click:".t-preference-button"},{wait:500},{if:{exists:".t-declineAllButton"},then:[{click:".t-declineAllButton"}],else:[{waitForThenClick:".t-preference-modal input[type=checkbox][checked]:not([disabled])",all:!0},{waitForThenClick:".t-saveButton"}]}]}]},{name:"termsfeed",vendorUrl:"https://termsfeed.com",comment:"v4.x.x",prehideSelectors:[".termsfeed-com---nb"],detectCmp:[{exists:".termsfeed-com---nb"}],detectPopup:[{visible:".termsfeed-com---nb"}],optIn:[{waitForThenClick:".cc-nb-okagree"}],optOut:[{waitForThenClick:".cc-nb-reject"}]},{name:"termsfeed3",vendorUrl:"https://termsfeed.com",comment:"v3.x.x",cosmetic:!0,prehideSelectors:[".cc_dialog.cc_css_reboot"],detectCmp:[{exists:".cc_dialog.cc_css_reboot"}],detectPopup:[{visible:".cc_dialog.cc_css_reboot"}],optIn:[{waitForThenClick:".cc_dialog.cc_css_reboot .cc_b_ok"}],optOut:[{hide:".cc_dialog.cc_css_reboot"}]},{name:"Test page cosmetic CMP",cosmetic:!0,prehideSelectors:["#privacy-test-page-cmp-test-prehide"],detectCmp:[{exists:"#privacy-test-page-cmp-test-banner"}],detectPopup:[{visible:"#privacy-test-page-cmp-test-banner"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{hide:"#privacy-test-page-cmp-test-banner"}],test:[{wait:500},{eval:"EVAL_TESTCMP_COSMETIC_0"}]},{name:"Test page CMP",prehideSelectors:["#reject-all"],detectCmp:[{exists:"#privacy-test-page-cmp-test"}],detectPopup:[{visible:"#privacy-test-page-cmp-test"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{waitFor:"#reject-all"},{click:"#reject-all"}],test:[{eval:"EVAL_TESTCMP_0"}]},{name:"thalia.de",prehideSelectors:[".consent-banner-box"],detectCmp:[{exists:"consent-banner[component=consent-banner]"}],detectPopup:[{visible:".consent-banner-box"}],optIn:[{click:".button-zustimmen"}],optOut:[{click:"button[data-consent=disagree]"}]},{name:"thefreedictionary.com",prehideSelectors:["#cmpBanner"],detectCmp:[{exists:"#cmpBanner"}],detectPopup:[{visible:"#cmpBanner"}],optIn:[{eval:"EVAL_THEFREEDICTIONARY_1"}],optOut:[{eval:"EVAL_THEFREEDICTIONARY_0"}]},{name:"theverge",runContext:{frame:!1,main:!0,urlPattern:"^https://(www)?\\.theverge\\.com"},intermediate:!1,prehideSelectors:[".duet--cta--cookie-banner"],detectCmp:[{exists:".duet--cta--cookie-banner"}],detectPopup:[{visible:".duet--cta--cookie-banner"}],optIn:[{click:".duet--cta--cookie-banner button.tracking-12",all:!1}],optOut:[{click:".duet--cta--cookie-banner button.tracking-12 > span"}],test:[{eval:"EVAL_THEVERGE_0"}]},{name:"tidbits-com",cosmetic:!0,prehideSelectors:["#eu_cookie_law_widget-2"],detectCmp:[{exists:"#eu_cookie_law_widget-2"}],detectPopup:[{visible:"#eu_cookie_law_widget-2"}],optIn:[{click:"#eu-cookie-law form > input.accept"}],optOut:[{hide:"#eu_cookie_law_widget-2"}]},{name:"tractor-supply",runContext:{urlPattern:"^https://www\\.tractorsupply\\.com/"},cosmetic:!0,prehideSelectors:[".tsc-cookie-banner"],detectCmp:[{exists:".tsc-cookie-banner"}],detectPopup:[{visible:".tsc-cookie-banner"}],optIn:[{click:"#cookie-banner-cancel"}],optOut:[{hide:".tsc-cookie-banner"}]},{name:"trader-joes-com",cosmetic:!0,prehideSelectors:['div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'],detectCmp:[{exists:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],detectPopup:[{visible:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],optIn:[{click:'div[class^="CookiesAlert_cookiesAlert__container__"] button'}],optOut:[{hide:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}]},{name:"transcend",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#transcend-consent-manager"],detectCmp:[{exists:"#transcend-consent-manager"}],detectPopup:[{visible:"#transcend-consent-manager"}],optIn:[{waitForThenClick:["#transcend-consent-manager","#consentManagerMainDialog .inner-container button"]}],optOut:[{hide:"#transcend-consent-manager"}]},{name:"transip-nl",runContext:{urlPattern:"^https://www\\.transip\\.nl/"},prehideSelectors:["#consent-modal"],detectCmp:[{any:[{exists:"#consent-modal"},{exists:"#privacy-settings-content"}]}],detectPopup:[{any:[{visible:"#consent-modal"},{visible:"#privacy-settings-content"}]}],optIn:[{click:'button[type="submit"]'}],optOut:[{if:{exists:"#privacy-settings-content"},then:[{click:'button[type="submit"]'}],else:[{click:"div.one-modal__action-footer-column--secondary > a"}]}]},{name:"tropicfeel-com",prehideSelectors:["#shopify-section-cookies-controller"],detectCmp:[{exists:"#shopify-section-cookies-controller"}],detectPopup:[{visible:"#shopify-section-cookies-controller #cookies-controller-main-pane",check:"any"}],optIn:[{waitForThenClick:"#cookies-controller-main-pane form[data-form-allow-all] button"}],optOut:[{click:"#cookies-controller-main-pane a[data-tab-target=manage-cookies]"},{waitFor:"#manage-cookies-pane.active"},{click:"#manage-cookies-pane.active input[type=checkbox][checked]:not([disabled])",all:!0},{click:"#manage-cookies-pane.active button[type=submit]"}],test:[]},{name:"true-car",runContext:{urlPattern:"^https://www\\.truecar\\.com/"},cosmetic:!0,prehideSelectors:[['div[aria-labelledby="cookie-banner-heading"]']],detectCmp:[{exists:'div[aria-labelledby="cookie-banner-heading"]'}],detectPopup:[{visible:'div[aria-labelledby="cookie-banner-heading"]'}],optIn:[{click:'div[aria-labelledby="cookie-banner-heading"] > button[aria-label="Close"]'}],optOut:[{hide:'div[aria-labelledby="cookie-banner-heading"]'}]},{name:"truyo",prehideSelectors:["#truyo-consent-module"],detectCmp:[{exists:"#truyo-cookieBarContent"}],detectPopup:[{visible:"#truyo-consent-module"}],optIn:[{click:"button#acceptAllCookieButton"}],optOut:[{click:"button#declineAllCookieButton"}]},{name:"tumblr-com",cosmetic:!0,prehideSelectors:["#cmp-app-container"],detectCmp:[{exists:"#cmp-app-container"}],detectPopup:[{visible:"#cmp-app-container"}],optIn:[{click:"#tumblr #cmp-app-container div.components-modal__frame > iframe > html body > div > div > div.cmp__dialog-footer > div > button.components-button.white-space-normal.is-primary"}],optOut:[{hide:"#cmp-app-container"}]},{name:"twitch-mobile",vendorUrl:"https://m.twitch.tv/",cosmetic:!0,runContext:{urlPattern:"^https?://m\\.twitch\\.tv"},prehideSelectors:[],detectCmp:[{exists:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],detectPopup:[{visible:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],optIn:[{waitForThenClick:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"]) button'}],optOut:[{hide:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"])'}]},{name:"twitch.tv",runContext:{urlPattern:"^https?://(www\\.)?twitch\\.tv"},prehideSelectors:["div:has(> .consent-banner .consent-banner__content--gdpr-v2),.ReactModalPortal:has([data-a-target=consent-modal-save])"],detectCmp:[{exists:".consent-banner .consent-banner__content--gdpr-v2"}],detectPopup:[{visible:".consent-banner .consent-banner__content--gdpr-v2"}],optIn:[{click:'button[data-a-target="consent-banner-accept"]'}],optOut:[{hide:"div:has(> .consent-banner .consent-banner__content--gdpr-v2)"},{click:'button[data-a-target="consent-banner-manage-preferences"]'},{waitFor:"input[type=checkbox][data-a-target=tw-checkbox]"},{click:"input[type=checkbox][data-a-target=tw-checkbox][checked]:not([disabled])",all:!0,optional:!0},{waitForThenClick:"[data-a-target=consent-modal-save]"},{waitForVisible:".ReactModalPortal:has([data-a-target=consent-modal-save])",check:"none"}]},{name:"twitter",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?twitter\\.com/"},prehideSelectors:['[data-testid="BottomBar"]'],detectCmp:[{exists:'[data-testid="BottomBar"] div'}],detectPopup:[{visible:'[data-testid="BottomBar"] div'}],optIn:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:first-child'}],optOut:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:last-child'}],TODOtest:[{eval:"EVAL_document.cookie.includes('d_prefs=MjoxLGNvbnNlbnRfdmVyc2lvbjoy')"}]},{name:"ubuntu.com",prehideSelectors:["dialog.cookie-policy"],detectCmp:[{any:[{exists:"dialog.cookie-policy header"},{exists:'xpath///*[@id="modal"]/div/header'}]}],detectPopup:[{any:[{visible:"dialog header"},{visible:'xpath///*[@id="modal"]/div/header'}]}],optIn:[{any:[{waitForThenClick:"#cookie-policy-button-accept"},{waitForThenClick:'xpath///*[@id="cookie-policy-button-accept"]'}]}],optOut:[{any:[{waitForThenClick:"button.js-manage"},{waitForThenClick:'xpath///*[@id="cookie-policy-content"]/p[4]/button[2]'}]},{waitForThenClick:"dialog.cookie-policy .p-switch__input:checked",optional:!0,all:!0,timeout:500},{any:[{waitForThenClick:"dialog.cookie-policy .js-save-preferences"},{waitForThenClick:'xpath///*[@id="modal"]/div/button'}]}],test:[{eval:"EVAL_UBUNTU_COM_0"}]},{name:"UK Cookie Consent",prehideSelectors:["#catapult-cookie-bar"],cosmetic:!0,detectCmp:[{exists:"#catapult-cookie-bar"}],detectPopup:[{exists:".has-cookie-bar #catapult-cookie-bar"}],optIn:[{click:"#catapultCookie"}],optOut:[{hide:"#catapult-cookie-bar"}],test:[{eval:"EVAL_UK_COOKIE_CONSENT_0"}]},{name:"urbanarmorgear-com",cosmetic:!0,prehideSelectors:['div[class^="Layout__CookieBannerContainer-"]'],detectCmp:[{exists:'div[class^="Layout__CookieBannerContainer-"]'}],detectPopup:[{visible:'div[class^="Layout__CookieBannerContainer-"]'}],optIn:[{click:'button[class^="CookieBanner__AcceptButton"]'}],optOut:[{hide:'div[class^="Layout__CookieBannerContainer-"]'}]},{name:"usercentrics-api",detectCmp:[{exists:"#usercentrics-root"}],detectPopup:[{eval:"EVAL_USERCENTRICS_API_0"},{exists:["#usercentrics-root","[data-testid=uc-container]"]},{waitForVisible:"#usercentrics-root",timeout:2e3}],optIn:[{eval:"EVAL_USERCENTRICS_API_3"},{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_5"}],optOut:[{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_2"}],test:[{eval:"EVAL_USERCENTRICS_API_6"}]},{name:"usercentrics-button",detectCmp:[{exists:"#usercentrics-button"}],detectPopup:[{visible:"#usercentrics-button #uc-btn-accept-banner"}],optIn:[{click:"#usercentrics-button #uc-btn-accept-banner"}],optOut:[{click:"#usercentrics-button #uc-btn-deny-banner"}],test:[{eval:"EVAL_USERCENTRICS_BUTTON_0"}]},{name:"uswitch.com",prehideSelectors:["#cookie-banner-wrapper"],detectCmp:[{exists:"#cookie-banner-wrapper"}],detectPopup:[{visible:"#cookie-banner-wrapper"}],optIn:[{click:"#cookie_banner_accept_mobile"}],optOut:[{click:"#cookie_banner_save"}]},{name:"vodafone.de",runContext:{urlPattern:"^https://www\\.vodafone\\.de/"},prehideSelectors:[".dip-consent,.dip-consent-container"],detectCmp:[{exists:".dip-consent-container"}],detectPopup:[{visible:".dip-consent-content"}],optOut:[{click:'.dip-consent-btn[tabindex="2"]'}],optIn:[{click:'.dip-consent-btn[tabindex="1"]'}]},{name:"waitrose.com",prehideSelectors:["div[aria-labelledby=CookieAlertModalHeading]","section[data-test=initial-waitrose-cookie-consent-banner]","section[data-test=cookie-consent-modal]"],detectCmp:[{exists:"section[data-test=initial-waitrose-cookie-consent-banner]"}],detectPopup:[{visible:"section[data-test=initial-waitrose-cookie-consent-banner]"}],optIn:[{click:"button[data-test=accept-all]"}],optOut:[{click:"button[data-test=manage-cookies]"},{wait:200},{eval:"EVAL_WAITROSE_0"},{click:"button[data-test=submit]"}],test:[{eval:"EVAL_WAITROSE_1"}]},{name:"webflow",vendorUrl:"https://webflow.com/",prehideSelectors:[".fs-cc-components"],detectCmp:[{exists:".fs-cc-components"}],detectPopup:[{visible:".fs-cc-components"},{visible:"[fs-cc=banner]"}],optIn:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=allow]"}],optOut:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=deny]"}]},{name:"wetransfer.com",detectCmp:[{exists:".welcome__cookie-notice"}],detectPopup:[{visible:".welcome__cookie-notice"}],optIn:[{click:".welcome__button--accept"}],optOut:[{click:".welcome__button--decline"}]},{name:"whitepages.com",runContext:{urlPattern:"^https://www\\.whitepages\\.com/"},cosmetic:!0,prehideSelectors:[".cookie-wrapper, .cookie-overlay"],detectCmp:[{exists:".cookie-wrapper"}],detectPopup:[{visible:".cookie-overlay"}],optIn:[{click:'button[aria-label="Got it"]'}],optOut:[{hide:".cookie-wrapper"}]},{name:"wolframalpha",vendorUrl:"https://www.wolframalpha.com",prehideSelectors:[],cosmetic:!0,runContext:{urlPattern:"^https://www\\.wolframalpha\\.com/"},detectCmp:[{exists:"section._a_yb"}],detectPopup:[{visible:"section._a_yb"}],optIn:[{waitForThenClick:"section._a_yb button"}],optOut:[{hide:"section._a_yb"}]},{name:"woo-commerce-com",prehideSelectors:[".wccom-comp-privacy-banner .wccom-privacy-banner"],detectCmp:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],detectPopup:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],optIn:[{click:".wccom-privacy-banner__content-buttons button.is-primary"}],optOut:[{click:".wccom-privacy-banner__content-buttons button.is-secondary"},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:"div.wccom-modal__footer > button"}]},{name:"WP Cookie Notice for GDPR",vendorUrl:"https://wordpress.org/plugins/gdpr-cookie-consent/",prehideSelectors:["#gdpr-cookie-consent-bar"],detectCmp:[{exists:"#gdpr-cookie-consent-bar"}],detectPopup:[{visible:"#gdpr-cookie-consent-bar"}],optIn:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_accept"}],optOut:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_reject"}],test:[{eval:"EVAL_WP_COOKIE_NOTICE_0"}]},{name:"wpcc",cosmetic:!0,prehideSelectors:[".wpcc-container"],detectCmp:[{exists:".wpcc-container"}],detectPopup:[{exists:".wpcc-container .wpcc-message"}],optIn:[{click:".wpcc-compliance .wpcc-btn"}],optOut:[{hide:".wpcc-container"}]},{name:"xe.com",vendorUrl:"https://www.xe.com/",runContext:{urlPattern:"^https://www\\.xe\\.com/"},prehideSelectors:["[class*=ConsentBanner]"],detectCmp:[{exists:"[class*=ConsentBanner]"}],detectPopup:[{visible:"[class*=ConsentBanner]"}],optIn:[{waitForThenClick:"[class*=ConsentBanner] .egnScw"}],optOut:[{wait:1e3},{waitForThenClick:"[class*=ConsentBanner] .frDWEu"},{waitForThenClick:"[class*=ConsentBanner] .hXIpFU"}],test:[{eval:"EVAL_XE_TEST"}]},{name:"xhamster-eu",prehideSelectors:[".cookies-modal"],detectCmp:[{exists:".cookies-modal"}],detectPopup:[{exists:".cookies-modal"}],optIn:[{click:"button.cmd-button-accept-all"}],optOut:[{click:"button.cmd-button-reject-all"}]},{name:"xhamster-us",runContext:{urlPattern:"^https://(www\\.)?xhamster\\d?\\.com"},cosmetic:!0,prehideSelectors:[".cookie-announce"],detectCmp:[{exists:".cookie-announce"}],detectPopup:[{visible:".cookie-announce .announce-text"}],optIn:[{click:".cookie-announce button.xh-button"}],optOut:[{hide:".cookie-announce"}]},{name:"xing.com",detectCmp:[{exists:"div[class^=cookie-consent-CookieConsent]"}],detectPopup:[{exists:"div[class^=cookie-consent-CookieConsent]"}],optIn:[{click:"#consent-accept-button"}],optOut:[{click:"#consent-settings-button"},{click:".consent-banner-button-accept-overlay"}],test:[{eval:"EVAL_XING_0"}]},{name:"xnxx-com",cosmetic:!0,prehideSelectors:["#cookies-use-alert"],detectCmp:[{exists:"#cookies-use-alert"}],detectPopup:[{visible:"#cookies-use-alert"}],optIn:[{click:"#cookies-use-alert .close"}],optOut:[{hide:"#cookies-use-alert"}]},{name:"xvideos",vendorUrl:"https://xvideos.com",runContext:{urlPattern:"^https://[^/]*xvideos\\.com/"},prehideSelectors:[],detectCmp:[{exists:".disclaimer-opened #disclaimer-cookies"}],detectPopup:[{visible:".disclaimer-opened #disclaimer-cookies"}],optIn:[{waitForThenClick:"#disclaimer-accept_cookies"}],optOut:[{waitForThenClick:"#disclaimer-reject_cookies"}]},{name:"Yahoo",runContext:{urlPattern:"^https://consent\\.yahoo\\.com/v2/"},prehideSelectors:["#reject-all"],detectCmp:[{exists:"#consent-page"}],detectPopup:[{visible:"#consent-page"}],optIn:[{waitForThenClick:"#consent-page button[value=agree]"}],optOut:[{waitForThenClick:"#consent-page button[value=reject]"}]},{name:"youporn.com",cosmetic:!0,prehideSelectors:[".euCookieModal, #js_euCookieModal"],detectCmp:[{exists:".euCookieModal"}],detectPopup:[{exists:".euCookieModal, #js_euCookieModal"}],optIn:[{click:'button[name="user_acceptCookie"]'}],optOut:[{hide:".euCookieModal"}]},{name:"youtube-desktop",prehideSelectors:["tp-yt-iron-overlay-backdrop.opened","ytd-consent-bump-v2-lightbox"],detectCmp:[{exists:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"},{exists:'ytd-consent-bump-v2-lightbox tp-yt-paper-dialog a[href^="https://consent.youtube.com/"]'}],detectPopup:[{visible:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"}],optIn:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child button"},{wait:500}],optOut:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_DESKTOP_0"}]},{name:"youtube-mobile",prehideSelectors:[".consent-bump-v2-lightbox"],detectCmp:[{exists:"ytm-consent-bump-v2-renderer"}],detectPopup:[{visible:"ytm-consent-bump-v2-renderer"}],optIn:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:first-child button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:first-child button"},{wait:500}],optOut:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:nth-child(2) button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:nth-child(2) button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_MOBILE_0"}]},{name:"zdf",prehideSelectors:["#zdf-cmp-banner-sdk"],detectCmp:[{exists:"#zdf-cmp-banner-sdk"}],detectPopup:[{visible:"#zdf-cmp-main.zdf-cmp-show"}],optIn:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-accept-btn"}],optOut:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-deny-btn"}],test:[]}],C={"didomi.io":{detectors:[{presentMatcher:{target:{selector:"#didomi-host, #didomi-notice"},type:"css"},showingMatcher:{target:{selector:"body.didomi-popup-open, .didomi-notice-banner"},type:"css"}}],methods:[{action:{target:{selector:".didomi-popup-notice-buttons .didomi-button:not(.didomi-button-highlight), .didomi-notice-banner .didomi-learn-more-button"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{retries:50,target:{selector:"#didomi-purpose-cookies"},type:"waitcss",waitTime:50},{consents:[{description:"Share (everything) with others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:last-child"},type:"click"},type:"X"},{description:"Information storage and access",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:last-child"},type:"click"},type:"D"},{description:"Content selection, offers and marketing",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:last-child"},type:"click"},type:"E"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:last-child"},type:"click"},type:"B"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:last-child"},type:"click"},type:"B"},{description:"Ad and content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection",falseAction:{parent:{childFilter:{target:{selector:"#didomi-purpose-pub-ciblee"}},selector:".didomi-consent-popup-data-processing, .didomi-components-accordion-label-container"},target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - basics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - partners and subsidiaries",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:last-child"},type:"click"},type:"F"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:last-child"},type:"click"},type:"A"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:last-child"},type:"click"},type:"A"},{description:"Content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:last-child"},type:"click"},type:"E"},{description:"Ad delivery",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:last-child"},type:"click"},type:"F"}],type:"consent"},{action:{consents:[{matcher:{childFilter:{target:{selector:":not(.didomi-components-radio__option--selected)"}},type:"css"},trueAction:{target:{selector:":nth-child(2)"},type:"click"},falseAction:{target:{selector:":first-child"},type:"click"},type:"X"}],type:"consent"},target:{selector:".didomi-components-radio"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".didomi-consent-popup-footer .didomi-consent-popup-actions"},target:{selector:".didomi-components-button:first-child"},type:"click"},name:"SAVE_CONSENT"}]},oil:{detectors:[{presentMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"},showingMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".as-js-advanced-settings"},type:"click"},{retries:"10",target:{selector:".as-oil-cpc__purpose-container"},type:"waitcss",waitTime:"250"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{consents:[{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"D"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"B"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:".as-oil__btn-optin"},type:"click"},name:"SAVE_CONSENT"},{action:{target:{selector:"div.as-oil"},type:"hide"},name:"HIDE_CMP"}]},optanon:{detectors:[{presentMatcher:{target:{selector:"#optanon-menu, .optanon-alert-box-wrapper"},type:"css"},showingMatcher:{target:{displayFilter:!0,selector:".optanon-alert-box-wrapper"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".optanon-alert-box-wrapper .optanon-toggle-display, a[onclick*='OneTrust.ToggleInfoDisplay()'], a[onclick*='Optanon.ToggleInfoDisplay()']"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".preference-menu-item #Your-privacy"},type:"click"},{target:{selector:"#optanon-vendor-consent-text"},type:"click"},{action:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},target:{selector:"#optanon-vendor-consent-list .vendor-item"},type:"foreach"},{target:{selector:".vendor-consent-back-link"},type:"click"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"D"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".optanon-save-settings-button"},target:{selector:".optanon-white-button-middle"},type:"click"},name:"SAVE_CONSENT"},{action:{actions:[{target:{selector:"#optanon-popup-wrapper"},type:"hide"},{target:{selector:"#optanon-popup-bg"},type:"hide"},{target:{selector:".optanon-alert-box-wrapper"},type:"hide"}],type:"list"},name:"HIDE_CMP"}]},quantcast2:{detectors:[{presentMatcher:{target:{selector:"[data-tracking-opt-in-overlay]"},type:"css"},showingMatcher:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"css"}}],methods:[{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{type:"wait",waitTime:500},{action:{actions:[{target:{selector:"div",textFilter:["Information storage and access"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"D"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Personalization"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Ad selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Content selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"E"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Measurement"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"B"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Other Partners"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},type:"ifcss"}],type:"list"},parent:{childFilter:{target:{selector:"input"}},selector:"[data-tracking-opt-in-overlay] > div > div"},target:{childFilter:{target:{selector:"input"}},selector:":scope > div"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-save]"},type:"click"},name:"SAVE_CONSENT"}]},springer:{detectors:[{presentMatcher:{parent:null,target:{selector:".cmp-app_gdpr"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".cmp-popup_popup"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".cmp-intro_rejectAll"},type:"click"},{type:"wait",waitTime:250},{target:{selector:".cmp-purposes_purposeItem:not(.cmp-purposes_selectedPurpose)"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{consents:[{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"D"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"}],type:"consent"},name:"DO_CONSENT"},{action:{target:{selector:".cmp-details_save"},type:"click"},name:"SAVE_CONSENT"}]},wordpressgdpr:{detectors:[{presentMatcher:{parent:null,target:{selector:".wpgdprc-consent-bar"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".wpgdprc-consent-bar"},type:"css"}}],methods:[{action:{parent:null,target:{selector:".wpgdprc-consent-bar .wpgdprc-consent-bar__settings",textFilter:null},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Eyeota"},type:"click"},{consents:[{description:"Eyeota Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Advertising"},type:"click"},{consents:[{description:"Advertising Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{parent:null,target:{selector:".wpgdprc-button",textFilter:"Save my settings"},type:"click"},name:"SAVE_CONSENT"}]}},v={autoconsent:w,consentomatic:C},f=Object.freeze({__proto__:null,autoconsent:w,consentomatic:C,default:v});const A=new class{constructor(e,t=null,o=null){if(this.id=n(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},a.sendContentMessage=e,this.sendContentMessage=e,this.rules=[],this.updateState({lifecycle:"loading"}),this.addDynamicRules(),t)this.initialize(t,o);else{o&&this.parseDeclarativeRules(o);e({type:"init",url:window.location.href}),this.updateState({lifecycle:"waitingForInitResponse"})}this.domActions=new class{constructor(e){this.autoconsentInstance=e}click(e,t=!1){const o=this.elementSelector(e);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[click]",e,t,o),o.length>0&&(t?o.forEach((e=>e.click())):o[0].click()),o.length>0}elementExists(e){return this.elementSelector(e).length>0}elementVisible(e,t){const o=this.elementSelector(e),c=new Array(o.length);return o.forEach(((e,t)=>{c[t]=k(e)})),"none"===t?c.every((e=>!e)):0!==c.length&&("any"===t?c.some((e=>e)):c.every((e=>e)))}waitForElement(e,t=1e4){const o=Math.ceil(t/200);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[waitForElement]",e),h((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return h((()=>this.elementVisible(e,o)),Math.ceil(t/200),200)}async waitForThenClick(e,t=1e4,o=!1){return await this.waitForElement(e,t),this.click(e,o)}wait(e){return new Promise((t=>{setTimeout((()=>{t(!0)}),e)}))}hide(e,t){return m(u(),e,t)}prehide(e){const t=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),m(t,e,"opacity")}undoPrehide(){const e=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[undoprehide]",e,location.href),e&&e.remove(),!!e}querySingleReplySelector(e,t=document){if(e.startsWith("aria/"))return[];if(e.startsWith("xpath/")){const o=e.slice(6),c=document.evaluate(o,t,null,XPathResult.ANY_TYPE,null);let i=null;const n=[];for(;i=c.iterateNext();)n.push(i);return n}return e.startsWith("text/")||e.startsWith("pierce/")?[]:t.shadowRoot?Array.from(t.shadowRoot.querySelectorAll(e)):Array.from(t.querySelectorAll(e))}querySelectorChain(e){let t,o=document;for(const c of e){if(t=this.querySingleReplySelector(c,o),0===t.length)return[];o=t[0]}return t}elementSelector(e){return"string"==typeof e?this.querySingleReplySelector(e):this.querySelectorChain(e)}}(this)}initialize(e,t){const o=b(e);if(o.logs.lifecycle&&console.log("autoconsent init",window.location.href),this.config=o,o.enabled){if(t&&this.parseDeclarativeRules(t),this.rules=function(e,t){return e.filter((e=>(!t.disabledCmps||!t.disabledCmps.includes(e.name))&&(t.enableCosmeticRules||!e.isCosmetic)))}(this.rules,o),e.enablePrehide)if(document.documentElement)this.prehideElements();else{const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.prehideElements()};window.addEventListener("DOMContentLoaded",e)}if("loading"===document.readyState){const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.start()};window.addEventListener("DOMContentLoaded",e)}else this.start();this.updateState({lifecycle:"initialized"})}else o.logs.lifecycle&&console.log("autoconsent is disabled")}addDynamicRules(){y.forEach((e=>{this.rules.push(new e(this))}))}parseDeclarativeRules(e){Object.keys(e.consentomatic).forEach((t=>{this.addConsentomaticCMP(t,e.consentomatic[t])})),e.autoconsent.forEach((e=>{this.addDeclarativeCMP(e)}))}addDeclarativeCMP(e){this.rules.push(new d(e,this))}addConsentomaticCMP(e,t){this.rules.push(new class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=l,this.isCosmetic=!1,t.methods.forEach((e=>{e.action&&this.methods.set(e.name,e.action)})),this.hasSelfTest=!1}get isIntermediate(){return!1}checkRunContext(){return!0}async detectCmp(){return this.config.detectors.map((e=>o(e.presentMatcher))).some((e=>!!e))}async detectPopup(){return this.config.detectors.map((e=>o(e.showingMatcher))).some((e=>!!e))}async executeAction(e,t){return!this.methods.has(e)||c(this.methods.get(e),t)}async optOut(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",[]),await this.executeAction("SAVE_CONSENT"),!0}async optIn(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",["D","A","B","E","F","X"]),await this.executeAction("SAVE_CONSENT"),!0}async openCmp(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),!0}async test(){return!0}}(`com_${e}`,t))}start(){window.requestIdleCallback?window.requestIdleCallback((()=>this._start()),{timeout:500}):this._start()}async _start(){const e=this.config.logs;e.lifecycle&&console.log(`Detecting CMPs on ${window.location.href}`),this.updateState({lifecycle:"started"});const t=await this.findCmp(this.config.detectRetries);if(this.updateState({detectedCmps:t.map((e=>e.name))}),0===t.length)return e.lifecycle&&console.log("no CMP found",location.href),this.config.enablePrehide&&this.undoPrehide(),this.updateState({lifecycle:"nothingDetected"}),!1;this.updateState({lifecycle:"cmpDetected"});const o=[],c=[];for(const e of t)e.isCosmetic?c.push(e):o.push(e);let i=!1,n=await this.detectPopups(o,(async e=>{i=await this.handlePopup(e)}));if(0===n.length&&(n=await this.detectPopups(c,(async e=>{i=await this.handlePopup(e)}))),0===n.length)return e.lifecycle&&console.log("no popup found"),this.config.enablePrehide&&this.undoPrehide(),!1;if(n.length>1){const t={msg:"Found multiple CMPs, check the detection rules.",cmps:n.map((e=>e.name))};e.errors&&console.warn(t.msg,t.cmps),this.sendContentMessage({type:"autoconsentError",details:t})}return i}async findCmp(e){const t=this.config.logs;this.updateState({findCmpAttempts:this.state.findCmpAttempts+1});const o=[];for(const e of this.rules)try{if(!e.checkRunContext())continue;await e.detectCmp()&&(t.lifecycle&&console.log(`Found CMP: ${e.name} ${window.location.href}`),this.sendContentMessage({type:"cmpDetected",url:location.href,cmp:e.name}),o.push(e))}catch(o){t.errors&&console.warn(`error detecting ${e.name}`,o)}return 0===o.length&&e>0?(await this.domActions.wait(500),this.findCmp(e-1)):o}async detectPopup(e){if(await this.waitForPopup(e).catch((t=>(this.config.logs.errors&&console.warn(`error waiting for a popup for ${e.name}`,t),!1))))return this.updateState({detectedPopups:this.state.detectedPopups.concat([e.name])}),this.sendContentMessage({type:"popupFound",cmp:e.name,url:location.href}),e;throw new Error("Popup is not shown")}async detectPopups(e,t){const o=e.map((e=>this.detectPopup(e)));await Promise.any(o).then((e=>{t(e)})).catch((()=>null));const c=await Promise.allSettled(o),i=[];for(const e of c)"fulfilled"===e.status&&i.push(e.value);return i}async handlePopup(e){return this.updateState({lifecycle:"openPopupDetected"}),this.config.enablePrehide&&!this.state.prehideOn&&this.prehideElements(),this.foundCmp=e,"optOut"===this.config.autoAction?await this.doOptOut():"optIn"===this.config.autoAction?await this.doOptIn():(this.config.logs.lifecycle&&console.log("waiting for opt-out signal...",location.href),!0)}async doOptOut(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptOut"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`),t=await this.foundCmp.optOut(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt out result ${t}`)):(e.errors&&console.log("no CMP to opt out"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optOutResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:this.foundCmp&&this.foundCmp.hasSelfTest,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optOutSucceeded":"optOutFailed"}),t}async doOptIn(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptIn"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`),t=await this.foundCmp.optIn(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt in result ${t}`)):(e.errors&&console.log("no CMP to opt in"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optInResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:!1,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optInSucceeded":"optInFailed"}),t}async doSelfTest(){const e=this.config.logs;let t;return this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`),t=await this.foundCmp.test()):(e.errors&&console.log("no CMP to self test"),t=!1),this.sendContentMessage({type:"selfTestResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,url:location.href}),this.updateState({selfTest:t}),t}async waitForPopup(e,t=5,o=500){const c=this.config.logs;c.lifecycle&&console.log("checking if popup is open...",e.name);const i=await e.detectPopup().catch((t=>(c.errors&&console.warn(`error detecting popup for ${e.name}`,t),!1)));return!i&&t>0?(await this.domActions.wait(o),this.waitForPopup(e,t-1,o)):(c.lifecycle&&console.log(e.name,"popup is "+(i?"open":"not open")),i)}prehideElements(){const e=this.config.logs,t=this.rules.reduce(((e,t)=>t.prehideSelectors?[...e,...t.prehideSelectors]:e),["#didomi-popup,.didomi-popup-container,.didomi-popup-notice,.didomi-consent-popup-preferences,#didomi-notice,.didomi-popup-backdrop,.didomi-screen-medium"]);return this.updateState({prehideOn:!0}),setTimeout((()=>{this.config.enablePrehide&&this.state.prehideOn&&!["runningOptOut","runningOptIn"].includes(this.state.lifecycle)&&(e.lifecycle&&console.log("Process is taking too long, unhiding elements"),this.undoPrehide())}),this.config.prehideTimeout||2e3),this.domActions.prehide(t.join(","))}undoPrehide(){return this.updateState({prehideOn:!1}),this.domActions.undoPrehide()}updateState(e){Object.assign(this.state,e),this.sendContentMessage({type:"report",instanceId:this.id,url:window.location.href,mainFrame:window.top===window.self,state:this.state})}async receiveMessageCallback(e){const t=this.config?.logs;switch(t?.messages&&console.log("received from background",e,window.location.href),e.type){case"initResp":this.initialize(e.config,e.rules);break;case"optIn":await this.doOptIn();break;case"optOut":await this.doOptOut();break;case"selfTest":await this.doSelfTest();break;case"evalResp":!function(e,t){const o=a.pending.get(e);o?(a.pending.delete(e),o.timer&&window.clearTimeout(o.timer),o.resolve(t)):console.warn("no eval #",e)}(e.id,e.result)}}}((e=>{window.webkit.messageHandlers[e.type]&&window.webkit.messageHandlers[e.type].postMessage(e).then((e=>{A.receiveMessageCallback(e)}))}),null,f);window.autoconsentMessageCallback=e=>{A.receiveMessageCallback(e)}}(); +!function(){"use strict";var e=class e{static setBase(t){e.base=t}static findElement(t,o=null,c=!1){let i=null;return i=null!=o?Array.from(o.querySelectorAll(t.selector)):null!=e.base?Array.from(e.base.querySelectorAll(t.selector)):Array.from(document.querySelectorAll(t.selector)),null!=t.textFilter&&(i=i.filter((e=>{const o=e.textContent.toLowerCase();if(Array.isArray(t.textFilter)){let e=!1;for(const c of t.textFilter)if(-1!==o.indexOf(c.toLowerCase())){e=!0;break}return e}if(null!=t.textFilter)return-1!==o.indexOf(t.textFilter.toLowerCase())}))),null!=t.styleFilters&&(i=i.filter((e=>{const o=window.getComputedStyle(e);let c=!0;for(const e of t.styleFilters){const t=o[e.option];c=e.negated?c&&t!==e.value:c&&t===e.value}return c}))),null!=t.displayFilter&&(i=i.filter((e=>t.displayFilter?0!==e.offsetHeight:0===e.offsetHeight))),null!=t.iframeFilter&&(i=i.filter((()=>t.iframeFilter?window.location!==window.parent.location:window.location===window.parent.location))),null!=t.childFilter&&(i=i.filter((o=>{const c=e.base;e.setBase(o);const i=e.find(t.childFilter);return e.setBase(c),null!=i.target}))),c?i:(i.length>1&&console.warn("Multiple possible targets: ",i,t,o),i[0])}static find(t,o=!1){const c=[];if(null!=t.parent){const i=e.findElement(t.parent,null,o);if(null!=i){if(i instanceof Array)return i.forEach((i=>{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})})),c;{const n=e.findElement(t.target,i,o);n instanceof Array?n.forEach((e=>{c.push({parent:i,target:e})})):c.push({parent:i,target:n})}}}else{const i=e.findElement(t.target,null,o);i instanceof Array?i.forEach((e=>{c.push({parent:null,target:e})})):c.push({parent:null,target:i})}return 0===c.length&&c.push({parent:null,target:null}),o?c:(1!==c.length&&console.warn("Multiple results found, even though multiple false",c),c[0])}};e.base=null;var t=e;function o(e){const o=t.find(e);return"css"===e.type?!!o.target:"checkbox"===e.type?!!o.target&&o.target.checked:void 0}async function c(e,n){switch(e.type){case"click":return async function(e){const o=t.find(e);null!=o.target&&o.target.click();return i(0)}(e);case"list":return async function(e,t){for(const o of e.actions)await c(o,t)}(e,n);case"consent":return async function(e,t){for(const i of e.consents){const e=-1!==t.indexOf(i.type);if(i.matcher&&i.toggleAction){o(i.matcher)!==e&&await c(i.toggleAction)}else e?await c(i.trueAction):await c(i.falseAction)}}(e,n);case"ifcss":return async function(e,o){const i=t.find(e);i.target?e.falseAction&&await c(e.falseAction,o):e.trueAction&&await c(e.trueAction,o)}(e,n);case"waitcss":return async function(e){await new Promise((o=>{let c=e.retries||10;const i=e.waitTime||250,n=()=>{const a=t.find(e);(e.negated&&a.target||!e.negated&&!a.target)&&c>0?(c-=1,setTimeout(n,i)):o()};n()}))}(e);case"foreach":return async function(e,o){const i=t.find(e,!0),n=t.base;for(const n of i)n.target&&(t.setBase(n.target),await c(e.action,o));t.setBase(n)}(e,n);case"hide":return async function(e){const o=t.find(e);o.target&&o.target.classList.add("Autoconsent-Hidden")}(e);case"slide":return async function(e){const o=t.find(e),c=t.find(e.dragTarget);if(o.target){const e=o.target.getBoundingClientRect(),t=c.target.getBoundingClientRect();let i=t.top-e.top,n=t.left-e.left;"y"===this.config.axis.toLowerCase()&&(n=0),"x"===this.config.axis.toLowerCase()&&(i=0);const a=window.screenX+e.left+e.width/2,s=window.screenY+e.top+e.height/2,r=e.left+e.width/2,l=e.top+e.height/2,p=document.createEvent("MouseEvents");p.initMouseEvent("mousedown",!0,!0,window,0,a,s,r,l,!1,!1,!1,!1,0,o.target);const d=document.createEvent("MouseEvents");d.initMouseEvent("mousemove",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target);const u=document.createEvent("MouseEvents");u.initMouseEvent("mouseup",!0,!0,window,0,a+n,s+i,r+n,l+i,!1,!1,!1,!1,0,o.target),o.target.dispatchEvent(p),await this.waitTimeout(10),o.target.dispatchEvent(d),await this.waitTimeout(10),o.target.dispatchEvent(u)}}(e);case"close":return async function(){window.close()}();case"wait":return async function(e){await i(e.waitTime)}(e);case"eval":return async function(e){return console.log("eval!",e.code),new Promise((t=>{try{e.async?(window.eval(e.code),setTimeout((()=>{t(window.eval("window.__consentCheckResult"))}),e.timeout||250)):t(window.eval(e.code))}catch(o){console.warn("eval error",o,e.code),t(!1)}}))}(e);default:throw"Unknown action type: "+e.type}}function i(e){return new Promise((t=>{setTimeout((()=>{t()}),e)}))}function n(){return crypto&&void 0!==crypto.randomUUID?crypto.randomUUID():Math.random().toString()}var a={pending:new Map,sendContentMessage:null};function s(e,t){const o=n();a.sendContentMessage({type:"eval",id:o,code:e,snippetId:t});const c=new class{constructor(e,t=1e3){this.id=e,this.promise=new Promise(((e,t)=>{this.resolve=e,this.reject=t})),this.timer=window.setTimeout((()=>{this.reject(new Error("timeout"))}),t)}}(o);return a.pending.set(c.id,c),c.promise}var r={EVAL_0:()=>console.log(1),EVAL_CONSENTMANAGER_1:()=>window.__cmp&&"object"==typeof __cmp("getCMPData"),EVAL_CONSENTMANAGER_2:()=>!__cmp("consentStatus").userChoiceExists,EVAL_CONSENTMANAGER_3:()=>__cmp("setConsent",0),EVAL_CONSENTMANAGER_4:()=>__cmp("setConsent",1),EVAL_CONSENTMANAGER_5:()=>__cmp("consentStatus").userChoiceExists,EVAL_COOKIEBOT_1:()=>!!window.Cookiebot,EVAL_COOKIEBOT_2:()=>!window.Cookiebot.hasResponse&&!0===window.Cookiebot.dialog?.visible,EVAL_COOKIEBOT_3:()=>window.Cookiebot.withdraw()||!0,EVAL_COOKIEBOT_4:()=>window.Cookiebot.hide()||!0,EVAL_COOKIEBOT_5:()=>!0===window.Cookiebot.declined,EVAL_KLARO_1:()=>{const e=globalThis.klaroConfig||globalThis.klaro?.getManager&&globalThis.klaro.getManager().config;if(!e)return!0;const t=(e.services||e.apps).filter((e=>!e.required)).map((e=>e.name));if(klaro&&klaro.getManager){const e=klaro.getManager();return t.every((t=>!e.consents[t]))}if(klaroConfig&&"cookie"===klaroConfig.storageMethod){const e=klaroConfig.cookieName||klaroConfig.storageName,o=JSON.parse(decodeURIComponent(document.cookie.split(";").find((t=>t.trim().startsWith(e))).split("=")[1]));return Object.keys(o).filter((e=>t.includes(e))).every((e=>!1===o[e]))}},EVAL_ONETRUST_1:()=>window.OnetrustActiveGroups.split(",").filter((e=>e.length>0)).length<=1,EVAL_TRUSTARC_TOP:()=>window&&window.truste&&"0"===window.truste.eu.bindMap.prefCookie,EVAL_ADROLL_0:()=>!document.cookie.includes("__adroll_fpc"),EVAL_ALMACMP_0:()=>document.cookie.includes('"name":"Google","consent":false'),EVAL_AFFINITY_SERIF_COM_0:()=>document.cookie.includes("serif_manage_cookies_viewed")&&!document.cookie.includes("serif_allow_analytics"),EVAL_ARBEITSAGENTUR_TEST:()=>document.cookie.includes("cookie_consent=denied"),EVAL_AXEPTIO_0:()=>document.cookie.includes("axeptio_authorized_vendors=%2C%2C"),EVAL_BAHN_TEST:()=>1===utag.gdpr.getSelectedCategories().length,EVAL_BING_0:()=>document.cookie.includes("AL=0")&&document.cookie.includes("AD=0")&&document.cookie.includes("SM=0"),EVAL_BLOCKSY_0:()=>document.cookie.includes("blocksy_cookies_consent_accepted=no"),EVAL_BORLABS_0:()=>!JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("borlabs-cookie"))).split("=",2)[1])).consents.statistics,EVAL_BUNDESREGIERUNG_DE_0:()=>document.cookie.match("cookie-allow-tracking=0"),EVAL_CANVA_0:()=>!document.cookie.includes("gtm_fpc_engagement_event"),EVAL_CC_BANNER2_0:()=>!!document.cookie.match(/sncc=[^;]+D%3Dtrue/),EVAL_CLICKIO_0:()=>document.cookie.includes("__lxG__consent__v2_daisybit="),EVAL_CLINCH_0:()=>document.cookie.includes("ctc_rejected=1"),EVAL_COOKIECONSENT2_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COOKIECONSENT3_TEST:()=>document.cookie.includes("cc_cookie="),EVAL_COINBASE_0:()=>JSON.parse(decodeURIComponent(document.cookie.match(/cm_(eu|default)_preferences=([0-9a-zA-Z\\{\\}\\[\\]%:]*);?/)[2])).consent.length<=1,EVAL_COMPLIANZ_BANNER_0:()=>document.cookie.includes("cmplz_banner-status=dismissed"),EVAL_COOKIE_LAW_INFO_0:()=>CLI.disableAllCookies()||CLI.reject_close()||!0,EVAL_COOKIE_LAW_INFO_1:()=>-1===document.cookie.indexOf("cookielawinfo-checkbox-non-necessary=yes"),EVAL_COOKIE_LAW_INFO_DETECT:()=>!!window.CLI,EVAL_COOKIE_MANAGER_POPUP_0:()=>!1===JSON.parse(document.cookie.split(";").find((e=>e.trim().startsWith("CookieLevel"))).split("=")[1]).social,EVAL_COOKIEALERT_0:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_1:()=>document.querySelector("body").removeAttribute("style")||!0,EVAL_COOKIEALERT_2:()=>!0===window.CookieConsent.declined,EVAL_COOKIEFIRST_0:()=>{return!1===(e=JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>-1!==e.indexOf("cookiefirst"))).trim()).split("=")[1])).performance&&!1===e.functional&&!1===e.advertising;var e},EVAL_COOKIEFIRST_1:()=>document.querySelectorAll("button[data-cookiefirst-accent-color=true][role=checkbox]:not([disabled])").forEach((e=>"true"==e.getAttribute("aria-checked")&&e.click()))||!0,EVAL_COOKIEINFORMATION_0:()=>CookieInformation.declineAllCategories()||!0,EVAL_COOKIEINFORMATION_1:()=>CookieInformation.submitAllCategories()||!0,EVAL_COOKIEINFORMATION_2:()=>document.cookie.includes("CookieInformationConsent="),EVAL_COOKIEYES_0:()=>document.cookie.includes("advertisement:no"),EVAL_DAILYMOTION_0:()=>!!document.cookie.match("dm-euconsent-v2"),EVAL_DNDBEYOND_TEST:()=>document.cookie.includes("cookie-consent=denied"),EVAL_DSGVO_0:()=>!document.cookie.includes("sp_dsgvo_cookie_settings"),EVAL_DUNELM_0:()=>document.cookie.includes("cc_functional=0")&&document.cookie.includes("cc_targeting=0"),EVAL_ETSY_0:()=>document.querySelectorAll(".gdpr-overlay-body input").forEach((e=>{e.checked=!1}))||!0,EVAL_ETSY_1:()=>document.querySelector(".gdpr-overlay-view button[data-wt-overlay-close]").click()||!0,EVAL_EU_COOKIE_COMPLIANCE_0:()=>-1===document.cookie.indexOf("cookie-agreed=2"),EVAL_EU_COOKIE_LAW_0:()=>!document.cookie.includes("euCookie"),EVAL_EZOIC_0:()=>ezCMP.handleAcceptAllClick(),EVAL_EZOIC_1:()=>!!document.cookie.match(/ez-consent-tcf/),EVAL_GOOGLE_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_HEMA_TEST_0:()=>document.cookie.includes("cookies_rejected=1"),EVAL_IUBENDA_0:()=>document.querySelectorAll(".purposes-item input[type=checkbox]:not([disabled])").forEach((e=>{e.checked&&e.click()}))||!0,EVAL_IUBENDA_1:()=>!!document.cookie.match(/_iub_cs-\d+=/),EVAL_IWINK_TEST:()=>document.cookie.includes("cookie_permission_granted=no"),EVAL_JQUERY_COOKIEBAR_0:()=>!document.cookie.includes("cookies-state=accepted"),EVAL_MEDIAVINE_0:()=>document.querySelectorAll('[data-name="mediavine-gdpr-cmp"] input[type=checkbox]').forEach((e=>e.checked&&e.click()))||!0,EVAL_MICROSOFT_0:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Reject|Ablehnen")))[0].click()||!0,EVAL_MICROSOFT_1:()=>Array.from(document.querySelectorAll("div > button")).filter((e=>e.innerText.match("Accept|Annehmen")))[0].click()||!0,EVAL_MICROSOFT_2:()=>!!document.cookie.match("MSCC|GHCC"),EVAL_MOOVE_0:()=>document.querySelectorAll("#moove_gdpr_cookie_modal input").forEach((e=>{e.disabled||(e.checked="moove_gdpr_strict_cookies"===e.name||"moove_gdpr_strict_cookies"===e.id)}))||!0,EVAL_ONENINETWO_0:()=>document.cookie.includes("CC_ADVERTISING=NO")&&document.cookie.includes("CC_ANALYTICS=NO"),EVAL_OPERA_0:()=>document.cookie.includes("cookie_consent_essential=true")&&!document.cookie.includes("cookie_consent_marketing=true"),EVAL_PAYPAL_0:()=>!0===document.cookie.includes("cookie_prefs"),EVAL_PRIMEBOX_0:()=>!document.cookie.includes("cb-enabled=accepted"),EVAL_PUBTECH_0:()=>document.cookie.includes("euconsent-v2")&&(document.cookie.match(/.YAAAAAAAAAAA/)||document.cookie.match(/.aAAAAAAAAAAA/)||document.cookie.match(/.YAAACFgAAAAA/)),EVAL_REDDIT_0:()=>document.cookie.includes("eu_cookie={%22opted%22:true%2C%22nonessential%22:false}"),EVAL_SIBBO_0:()=>!!window.localStorage.getItem("euconsent-v2"),EVAL_SIRDATA_UNBLOCK_SCROLL:()=>(document.documentElement.classList.forEach((e=>{e.startsWith("sd-cmp-")&&document.documentElement.classList.remove(e)})),!0),EVAL_SNIGEL_0:()=>!!document.cookie.match("snconsent"),EVAL_STEAMPOWERED_0:()=>2===JSON.parse(decodeURIComponent(document.cookie.split(";").find((e=>e.trim().startsWith("cookieSettings"))).split("=")[1])).preference_state,EVAL_SVT_TEST:()=>document.cookie.includes('cookie-consent-1={"optedIn":true,"functionality":false,"statistics":false}'),EVAL_TAKEALOT_0:()=>document.body.classList.remove("freeze")||(document.body.style="")||!0,EVAL_TARTEAUCITRON_0:()=>tarteaucitron.userInterface.respondAll(!1)||!0,EVAL_TARTEAUCITRON_1:()=>tarteaucitron.userInterface.respondAll(!0)||!0,EVAL_TARTEAUCITRON_2:()=>document.cookie.match(/tarteaucitron=[^;]*/)[0].includes("false"),EVAL_TAUNTON_TEST:()=>document.cookie.includes("taunton_user_consent_submitted=true"),EVAL_TEALIUM_0:()=>void 0!==window.utag&&"object"==typeof utag.gdpr,EVAL_TEALIUM_1:()=>utag.gdpr.setConsentValue(!1)||!0,EVAL_TEALIUM_DONOTSELL:()=>utag.gdpr.dns?.setDnsState(!1)||!0,EVAL_TEALIUM_2:()=>utag.gdpr.setConsentValue(!0)||!0,EVAL_TEALIUM_3:()=>1!==utag.gdpr.getConsentState(),EVAL_TEALIUM_DONOTSELL_CHECK:()=>1!==utag.gdpr.dns?.getDnsState(),EVAL_TESTCMP_0:()=>"button_clicked"===window.results.results[0],EVAL_TESTCMP_COSMETIC_0:()=>"banner_hidden"===window.results.results[0],EVAL_THEFREEDICTIONARY_0:()=>cmpUi.showPurposes()||cmpUi.rejectAll()||!0,EVAL_THEFREEDICTIONARY_1:()=>cmpUi.allowAll()||!0,EVAL_THEVERGE_0:()=>document.cookie.includes("_duet_gdpr_acknowledged=1"),EVAL_UBUNTU_COM_0:()=>document.cookie.includes("_cookies_accepted=essential"),EVAL_UK_COOKIE_CONSENT_0:()=>!document.cookie.includes("catAccCookies"),EVAL_USERCENTRICS_API_0:()=>"object"==typeof UC_UI,EVAL_USERCENTRICS_API_1:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_2:()=>!!UC_UI.denyAllConsents(),EVAL_USERCENTRICS_API_3:()=>!!UC_UI.acceptAllConsents(),EVAL_USERCENTRICS_API_4:()=>!!UC_UI.closeCMP(),EVAL_USERCENTRICS_API_5:()=>!0===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_API_6:()=>!1===UC_UI.areAllConsentsAccepted(),EVAL_USERCENTRICS_BUTTON_0:()=>JSON.parse(localStorage.getItem("usercentrics")).consents.every((e=>e.isEssential||!e.consentStatus)),EVAL_WAITROSE_0:()=>Array.from(document.querySelectorAll("label[id$=cookies-deny-label]")).forEach((e=>e.click()))||!0,EVAL_WAITROSE_1:()=>document.cookie.includes("wtr_cookies_advertising=0")&&document.cookie.includes("wtr_cookies_analytics=0"),EVAL_WP_COOKIE_NOTICE_0:()=>document.cookie.includes("wpl_viewed_cookie=no"),EVAL_XE_TEST:()=>document.cookie.includes("xeConsentState={%22performance%22:false%2C%22marketing%22:false%2C%22compliance%22:false}"),EVAL_XING_0:()=>document.cookie.includes("userConsent=%7B%22marketing%22%3Afalse"),EVAL_YOUTUBE_DESKTOP_0:()=>!!document.cookie.match(/SOCS=CAE/),EVAL_YOUTUBE_MOBILE_0:()=>!!document.cookie.match(/SOCS=CAE/)};var l={main:!0,frame:!1,urlPattern:""},p=class{constructor(e){this.runContext=l,this.autoconsent=e}get hasSelfTest(){throw new Error("Not Implemented")}get isIntermediate(){throw new Error("Not Implemented")}get isCosmetic(){throw new Error("Not Implemented")}mainWorldEval(e){const t=r[e];if(!t)return console.warn("Snippet not found",e),Promise.resolve(!1);const o=this.autoconsent.config.logs;if(this.autoconsent.config.isMainWorld){o.evals&&console.log("inline eval:",e,t);let c=!1;try{c=!!t.call(globalThis)}catch(t){o.evals&&console.error("error evaluating rule",e,t)}return Promise.resolve(c)}const c=`(${t.toString()})()`;return o.evals&&console.log("async eval:",e,c),s(c,e).catch((t=>(o.evals&&console.error("error evaluating rule",e,t),!1)))}checkRunContext(){const e={...l,...this.runContext},t=window.top===window;return!(t&&!e.main)&&(!(!t&&!e.frame)&&!(e.urlPattern&&!window.location.href.match(e.urlPattern)))}detectCmp(){throw new Error("Not Implemented")}async detectPopup(){return!1}optOut(){throw new Error("Not Implemented")}optIn(){throw new Error("Not Implemented")}openCmp(){throw new Error("Not Implemented")}async test(){return Promise.resolve(!0)}click(e,t=!1){return this.autoconsent.domActions.click(e,t)}elementExists(e){return this.autoconsent.domActions.elementExists(e)}elementVisible(e,t){return this.autoconsent.domActions.elementVisible(e,t)}waitForElement(e,t){return this.autoconsent.domActions.waitForElement(e,t)}waitForVisible(e,t,o){return this.autoconsent.domActions.waitForVisible(e,t,o)}waitForThenClick(e,t,o){return this.autoconsent.domActions.waitForThenClick(e,t,o)}wait(e){return this.autoconsent.domActions.wait(e)}hide(e,t){return this.autoconsent.domActions.hide(e,t)}prehide(e){return this.autoconsent.domActions.prehide(e)}undoPrehide(){return this.autoconsent.domActions.undoPrehide()}querySingleReplySelector(e,t){return this.autoconsent.domActions.querySingleReplySelector(e,t)}querySelectorChain(e){return this.autoconsent.domActions.querySelectorChain(e)}elementSelector(e){return this.autoconsent.domActions.elementSelector(e)}},d=class extends p{constructor(e,t){super(t),this.rule=e,this.name=e.name,this.runContext=e.runContext||l}get hasSelfTest(){return!!this.rule.test}get isIntermediate(){return!!this.rule.intermediate}get isCosmetic(){return!!this.rule.cosmetic}get prehideSelectors(){return this.rule.prehideSelectors}async detectCmp(){return!!this.rule.detectCmp&&this._runRulesParallel(this.rule.detectCmp)}async detectPopup(){return!!this.rule.detectPopup&&this._runRulesSequentially(this.rule.detectPopup)}async optOut(){const e=this.autoconsent.config.logs;return!!this.rule.optOut&&(e.lifecycle&&console.log("Initiated optOut()",this.rule.optOut),this._runRulesSequentially(this.rule.optOut))}async optIn(){const e=this.autoconsent.config.logs;return!!this.rule.optIn&&(e.lifecycle&&console.log("Initiated optIn()",this.rule.optIn),this._runRulesSequentially(this.rule.optIn))}async openCmp(){return!!this.rule.openCmp&&this._runRulesSequentially(this.rule.openCmp)}async test(){return this.hasSelfTest?this._runRulesSequentially(this.rule.test):super.test()}async evaluateRuleStep(e){const t=[],o=this.autoconsent.config.logs;if(e.exists&&t.push(this.elementExists(e.exists)),e.visible&&t.push(this.elementVisible(e.visible,e.check)),e.eval){const o=this.mainWorldEval(e.eval);t.push(o)}if(e.waitFor&&t.push(this.waitForElement(e.waitFor,e.timeout)),e.waitForVisible&&t.push(this.waitForVisible(e.waitForVisible,e.timeout,e.check)),e.click&&t.push(this.click(e.click,e.all)),e.waitForThenClick&&t.push(this.waitForThenClick(e.waitForThenClick,e.timeout,e.all)),e.wait&&t.push(this.wait(e.wait)),e.hide&&t.push(this.hide(e.hide,e.method)),e.if){if(!e.if.exists&&!e.if.visible)return console.error("invalid conditional rule",e.if),!1;const c=await this.evaluateRuleStep(e.if);o.rulesteps&&console.log("Condition is",c),c?t.push(this._runRulesSequentially(e.then)):e.else?t.push(this._runRulesSequentially(e.else)):t.push(!0)}if(e.any){for(const t of e.any)if(await this.evaluateRuleStep(t))return!0;return!1}if(0===t.length)return o.errors&&console.warn("Unrecognized rule",e),!1;return(await Promise.all(t)).reduce(((e,t)=>e&&t),!0)}async _runRulesParallel(e){const t=e.map((e=>this.evaluateRuleStep(e)));return(await Promise.all(t)).every((e=>!!e))}async _runRulesSequentially(e){const t=this.autoconsent.config.logs;for(const o of e){t.rulesteps&&console.log("Running rule...",o);const e=await this.evaluateRuleStep(o);if(t.rulesteps&&console.log("...rule result",e),!e&&!o.optional)return!1}return!0}};function u(e="autoconsent-css-rules"){const t=`style#${e}`,o=document.querySelector(t);if(o&&o instanceof HTMLStyleElement)return o;{const t=document.head||document.getElementsByTagName("head")[0]||document.documentElement,o=document.createElement("style");return o.id=e,t.appendChild(o),o}}function m(e,t,o="display"){const c=`${t} { ${"opacity"===o?"opacity: 0":"display: none"} !important; z-index: -1 !important; pointer-events: none !important; } `;return e instanceof HTMLStyleElement&&(e.innerText+=c,t.length>0)}async function h(e,t,o){const c=await e();return!c&&t>0?new Promise((c=>{setTimeout((async()=>{c(h(e,t-1,o))}),o)})):Promise.resolve(c)}function k(e){if(!e)return!1;if(null!==e.offsetParent)return!0;{const t=window.getComputedStyle(e);if("fixed"===t.position&&"none"!==t.display)return!0}return!1}function b(e){const t={enabled:!0,autoAction:"optOut",disabledCmps:[],enablePrehide:!0,enableCosmeticRules:!0,detectRetries:20,isMainWorld:!1,prehideTimeout:2e3,logs:{lifecycle:!1,rulesteps:!1,evals:!1,errors:!0,messages:!1}},o=(c=t,globalThis.structuredClone?structuredClone(c):JSON.parse(JSON.stringify(c)));var c;for(const c of Object.keys(t))void 0!==e[c]&&(o[c]=e[c]);return o}var _="#truste-show-consent",g="#truste-consent-track",y=[class extends p{constructor(e){super(e),this.name="TrustArc-top",this.prehideSelectors=[".trustarc-banner-container",`.truste_popframe,.truste_overlay,.truste_box_overlay,${g}`],this.runContext={main:!0,frame:!1},this._shortcutButton=null,this._optInDone=!1}get hasSelfTest(){return!1}get isIntermediate(){return!this._optInDone&&!this._shortcutButton}get isCosmetic(){return!1}async detectCmp(){const e=this.elementExists(`${_},${g}`);return e&&(this._shortcutButton=document.querySelector("#truste-consent-required")),e}async detectPopup(){return this.elementVisible(`#truste-consent-content,#trustarc-banner-overlay,${g}`,"all")}openFrame(){this.click(_)}async optOut(){return this._shortcutButton?(this._shortcutButton.click(),!0):(m(u(),`.truste_popframe, .truste_overlay, .truste_box_overlay, ${g}`),this.click(_),setTimeout((()=>{u().remove()}),1e4),!0)}async optIn(){return this._optInDone=!0,this.click("#truste-consent-button")}async openCmp(){return!0}async test(){return await this.mainWorldEval("EVAL_TRUSTARC_TOP")}},class extends p{constructor(){super(...arguments),this.name="TrustArc-frame",this.runContext={main:!1,frame:!0,urlPattern:"^https://consent-pref\\.trustarc\\.com/\\?"}}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return!0}async detectPopup(){return this.elementVisible("#defaultpreferencemanager","any")&&this.elementVisible(".mainContent","any")}async navigateToSettings(){return await h((async()=>this.elementExists(".shp")||this.elementVisible(".advance","any")||this.elementExists(".switch span:first-child")),10,500),this.elementExists(".shp")&&this.click(".shp"),await this.waitForElement(".prefPanel",5e3),this.elementVisible(".advance","any")&&this.click(".advance"),await h((()=>this.elementVisible(".switch span:first-child","any")),5,1e3)}async optOut(){return await h((()=>"complete"===document.readyState),20,100),await this.waitForElement(".mainContent[aria-hidden=false]",5e3),!!this.click(".rejectAll")||(this.elementExists(".prefPanel")&&await this.waitForElement('.prefPanel[style="visibility: visible;"]',3e3),this.click("#catDetails0")?(this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",5e3),!0):this.click(".required")?(this.waitForThenClick("#gwt-debug-close_id",5e3),!0):(await this.navigateToSettings(),this.click(".switch span:nth-child(1):not(.active)",!0),this.click(".submit"),this.waitForThenClick("#gwt-debug-close_id",3e5),!0))}async optIn(){return this.click(".call")||(await this.navigateToSettings(),this.click(".switch span:nth-child(2)",!0),this.click(".submit"),this.waitForElement("#gwt-debug-close_id",3e5).then((()=>{this.click("#gwt-debug-close_id")}))),!0}},class extends p{constructor(){super(...arguments),this.name="Cybotcookiebot",this.prehideSelectors=["#CybotCookiebotDialog,#CybotCookiebotDialogBodyUnderlay,#dtcookie-container,#cookiebanner,#cb-cookieoverlay,.modal--cookie-banner,#cookiebanner_outer,#CookieBanner"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return await this.mainWorldEval("EVAL_COOKIEBOT_1")}async detectPopup(){return this.mainWorldEval("EVAL_COOKIEBOT_2")}async optOut(){await this.wait(500);let e=await this.mainWorldEval("EVAL_COOKIEBOT_3");return await this.wait(500),e=e&&await this.mainWorldEval("EVAL_COOKIEBOT_4"),e}async optIn(){return this.elementExists("#dtcookie-container")?this.click(".h-dtcookie-accept"):(this.click(".CybotCookiebotDialogBodyLevelButton:not(:checked):enabled",!0),this.click("#CybotCookiebotDialogBodyLevelButtonAccept"),this.click("#CybotCookiebotDialogBodyButtonAccept"),!0)}async test(){return await this.wait(500),await this.mainWorldEval("EVAL_COOKIEBOT_5")}},class extends p{constructor(){super(...arguments),this.name="Sourcepoint-frame",this.prehideSelectors=["div[id^='sp_message_container_'],.message-overlay","#sp_privacy_manager_container"],this.ccpaNotice=!1,this.ccpaPopup=!1,this.runContext={main:!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 h((()=>this.isManagerOpen()),200,100)}await this.waitForElement(".type-modal",2e4),this.waitForThenClick(".ccpa-stack .pm-switch[aria-checked=true] .slider",500,!0);try{const e=".sp_choice_type_REJECT_ALL",t=".reject-toggle",o=await Promise.race([this.waitForElement(e,2e3).then((e=>e?0:-1)),this.waitForElement(t,2e3).then((e=>e?1:-1)),this.waitForElement(".pm-features",2e3).then((e=>e?2:-1))]);if(0===o)return await this.wait(1500),this.click(e);1===o?this.click(t):2===o&&(await this.waitForElement(".pm-features",1e4),this.click(".checked > span",!0),this.click(".chevron"))}catch(t){e.errors&&console.warn(t)}return this.click(".sp_choice_type_SAVE_AND_EXIT")}},class extends p{constructor(){super(...arguments),this.name="consentmanager.net",this.prehideSelectors=["#cmpbox,#cmpbox2"],this.apiAvailable=!1}get hasSelfTest(){return this.apiAvailable}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.apiAvailable=await this.mainWorldEval("EVAL_CONSENTMANAGER_1"),!!this.apiAvailable||this.elementExists("#cmpbox")}async detectPopup(){return this.apiAvailable?(await this.wait(500),await this.mainWorldEval("EVAL_CONSENTMANAGER_2")):this.elementVisible("#cmpbox .cmpmore","any")}async optOut(){return await this.wait(500),this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_3"):!!this.click(".cmpboxbtnno")||(this.elementExists(".cmpwelcomeprpsbtn")?(this.click(".cmpwelcomeprpsbtn > a[aria-checked=true]",!0),this.click(".cmpboxbtnsave"),!0):(this.click(".cmpboxbtncustom"),await this.waitForElement(".cmptblbox",2e3),this.click(".cmptdchoice > a[aria-checked=true]",!0),this.click(".cmpboxbtnyescustomchoices"),this.hide("#cmpwrapper,#cmpbox","display"),!0))}async optIn(){return this.apiAvailable?await this.mainWorldEval("EVAL_CONSENTMANAGER_4"):this.click(".cmpboxbtnyes")}async test(){if(this.apiAvailable)return await this.mainWorldEval("EVAL_CONSENTMANAGER_5")}},class extends p{constructor(){super(...arguments),this.name="Evidon"}get hasSelfTest(){return!1}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#_evidon_banner")}async detectPopup(){return this.elementVisible("#_evidon_banner","any")}async optOut(){return this.click("#_evidon-decline-button")||(m(u(),"#evidon-prefdiag-overlay,#evidon-prefdiag-background"),this.click("#_evidon-option-button"),await this.waitForElement("#evidon-prefdiag-overlay",5e3),this.click("#evidon-prefdiag-decline")),!0}async optIn(){return this.click("#_evidon-accept-button")}},class extends p{constructor(){super(...arguments),this.name="Onetrust",this.prehideSelectors=["#onetrust-banner-sdk,#onetrust-consent-sdk,.onetrust-pc-dark-filter,.js-consent-banner"],this.runContext={urlPattern:"^(?!.*https://www\\.nba\\.com/)"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("#onetrust-banner-sdk,#onetrust-pc-sdk")}async detectPopup(){return this.elementVisible("#onetrust-banner-sdk,#onetrust-pc-sdk","any")}async optOut(){return this.elementVisible("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies","any")?this.click("#onetrust-reject-all-handler,.ot-pc-refuse-all-handler,.js-reject-cookies"):(this.elementExists("#onetrust-pc-btn-handler")?this.click("#onetrust-pc-btn-handler"):this.click(".ot-sdk-show-settings,button.js-cookie-settings"),await this.waitForElement("#onetrust-consent-sdk",2e3),await this.wait(1e3),this.click("#onetrust-consent-sdk input.category-switch-handler:checked,.js-editor-toggle-state:checked",!0),await this.wait(1e3),await this.waitForElement(".save-preference-btn-handler,.js-consent-save",2e3),this.click(".save-preference-btn-handler,.js-consent-save"),await this.waitForVisible("#onetrust-banner-sdk",5e3,"none"),!0)}async optIn(){return this.click("#onetrust-accept-btn-handler,#accept-recommended-btn-handler,.js-accept-cookies")}async test(){return await h((()=>this.mainWorldEval("EVAL_ONETRUST_1")),10,500)}},class extends p{constructor(){super(...arguments),this.name="Klaro",this.prehideSelectors=[".klaro"],this.settingsOpen=!1}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".klaro > .cookie-modal")?(this.settingsOpen=!0,!0):this.elementExists(".klaro > .cookie-notice")}async detectPopup(){return this.elementVisible(".klaro > .cookie-notice,.klaro > .cookie-modal","any")}async optOut(){return!!this.click(".klaro .cn-decline")||(this.settingsOpen||(this.click(".klaro .cn-learn-more,.klaro .cm-button-manage"),await this.waitForElement(".klaro > .cookie-modal",2e3),this.settingsOpen=!0),!!this.click(".klaro .cn-decline")||(this.click(".cm-purpose:not(.cm-toggle-all) > input:not(.half-checked,.required,.only-required),.cm-purpose:not(.cm-toggle-all) > div > input:not(.half-checked,.required,.only-required)",!0),this.click(".cm-btn-accept,.cm-button")))}async optIn(){return!!this.click(".klaro .cm-btn-accept-all")||(this.settingsOpen?(this.click(".cm-purpose:not(.cm-toggle-all) > input.half-checked",!0),this.click(".cm-btn-accept")):this.click(".klaro .cookie-notice .cm-btn-success"))}async test(){return await this.mainWorldEval("EVAL_KLARO_1")}},class extends p{constructor(){super(...arguments),this.name="Uniconsent"}get prehideSelectors(){return[".unic",".modal:has(.unic)"]}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".unic .unic-box,.unic .unic-bar,.unic .unic-modal")}async detectPopup(){return this.elementVisible(".unic .unic-box,.unic .unic-bar,.unic .unic-modal","any")}async optOut(){if(await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic button").forEach((e=>{const t=e.textContent;(t.includes("Manage Options")||t.includes("Optionen verwalten"))&&e.click()})),await this.waitForElement(".unic input[type=checkbox]",1e3)){await this.waitForElement(".unic button",1e3),document.querySelectorAll(".unic input[type=checkbox]").forEach((e=>{e.checked&&e.click()}));for(const e of document.querySelectorAll(".unic button")){const t=e.textContent;for(const o of["Confirm Choices","Save Choices","Auswahl speichern"])if(t.includes(o))return e.click(),await this.wait(500),!0}}return!1}async optIn(){return this.waitForThenClick(".unic #unic-agree")}async test(){await this.wait(1e3);return!this.elementExists(".unic .unic-box,.unic .unic-bar")}},class extends p{constructor(){super(...arguments),this.prehideSelectors=[".cmp-root"],this.name="Conversant"}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists(".cmp-root .cmp-receptacle")}async detectPopup(){return this.elementVisible(".cmp-root .cmp-receptacle","any")}async optOut(){if(!await this.waitForThenClick(".cmp-main-button:not(.cmp-main-button--primary)"))return!1;if(!await this.waitForElement(".cmp-view-tab-tabs"))return!1;await this.waitForThenClick(".cmp-view-tab-tabs > :first-child"),await this.waitForThenClick(".cmp-view-tab-tabs > .cmp-view-tab--active:first-child");for(const e of Array.from(document.querySelectorAll(".cmp-accordion-item"))){e.querySelector(".cmp-accordion-item-title").click(),await h((()=>!!e.querySelector(".cmp-accordion-item-content.cmp-active")),10,50);const t=e.querySelector(".cmp-accordion-item-content.cmp-active");t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-deny:not(.cmp-toggle-deny--active)").forEach((e=>e.click())),t.querySelectorAll(".cmp-toggle-actions .cmp-toggle-checkbox:not(.cmp-toggle-checkbox--active)").forEach((e=>e.click()))}return await this.click(".cmp-main-button:not(.cmp-main-button--primary)"),!0}async optIn(){return this.waitForThenClick(".cmp-main-button.cmp-main-button--primary")}async test(){return document.cookie.includes("cmp-data=0")}},class extends p{constructor(){super(...arguments),this.name="tiktok.com",this.runContext={urlPattern:"tiktok"}}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}getShadowRoot(){const e=document.querySelector("tiktok-cookie-banner");return e?e.shadowRoot:null}async detectCmp(){return this.elementExists("tiktok-cookie-banner")}async detectPopup(){return k(this.getShadowRoot().querySelector(".tiktok-cookie-banner"))}async optOut(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:first-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no decline button found"),!1)}async optIn(){const e=this.autoconsent.config.logs,t=this.getShadowRoot().querySelector(".button-wrapper button:last-child");return t?(e.rulesteps&&console.log("[clicking]",t),t.click(),!0):(e.errors&&console.log("no accept button found"),!1)}async test(){const e=document.cookie.match(/cookie-consent=([^;]+)/);if(!e)return!1;const t=JSON.parse(decodeURIComponent(e[1]));return Object.values(t).every((e=>"boolean"!=typeof e||!1===e))}},class extends p{constructor(){super(...arguments),this.runContext={urlPattern:"^https://(www\\.)?airbnb\\.[^/]+/"},this.prehideSelectors=["div[data-testid=main-cookies-banner-container]",'div:has(> div:first-child):has(> div:last-child):has(> section [data-testid="strictly-necessary-cookies"])']}get hasSelfTest(){return!0}get isIntermediate(){return!1}get isCosmetic(){return!1}async detectCmp(){return this.elementExists("div[data-testid=main-cookies-banner-container]")}async detectPopup(){return this.elementVisible("div[data-testid=main-cookies-banner-container","any")}async optOut(){let e;for(await this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._snbhip0");e=document.querySelector("[data-testid=modal-container] button[aria-checked=true]:not([disabled])");)e.click();return this.waitForThenClick("button[data-testid=save-btn]")}async optIn(){return this.waitForThenClick("div[data-testid=main-cookies-banner-container] button._148dgdpk")}async test(){return await h((()=>!!document.cookie.match("OptanonAlertBoxClosed")),20,200)}},class extends p{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 h((()=>!!document.querySelector("#cmp-app-container iframe").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)}}];var w=[{name:"192.com",detectCmp:[{exists:".ont-cookies"}],detectPopup:[{visible:".ont-cookies"}],optIn:[{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-ok2"}],optOut:[{click:".ont-cookes-btn-manage"},{click:".ont-btn-main.ont-cookies-btn.js-ont-btn-choose"}],test:[{eval:"EVAL_ONENINETWO_0"}]},{name:"1password-com",cosmetic:!0,prehideSelectors:['footer #footer-root [aria-label="Cookie Consent"]'],detectCmp:[{exists:'footer #footer-root [aria-label="Cookie Consent"]'}],detectPopup:[{visible:'footer #footer-root [aria-label="Cookie Consent"]'}],optIn:[{click:'footer #footer-root [aria-label="Cookie Consent"] button'}],optOut:[{hide:'footer #footer-root [aria-label="Cookie Consent"]'}]},{name:"abconcerts.be",vendorUrl:"https://unknown",intermediate:!1,prehideSelectors:["dialog.cookie-consent"],detectCmp:[{exists:"dialog.cookie-consent form.cookie-consent__form"}],detectPopup:[{visible:"dialog.cookie-consent form.cookie-consent__form"}],optIn:[{waitForThenClick:"dialog.cookie-consent form.cookie-consent__form button[value=yes]"}],optOut:[{if:{exists:"dialog.cookie-consent form.cookie-consent__form button[value=no]"},then:[{click:"dialog.cookie-consent form.cookie-consent__form button[value=no]"}],else:[{click:"dialog.cookie-consent form.cookie-consent__form button.cookie-consent__options-toggle"},{waitForThenClick:'dialog.cookie-consent form.cookie-consent__form button[value="save_options"]'}]}]},{name:"activobank.pt",runContext:{urlPattern:"^https://(www\\.)?activobank\\.pt"},prehideSelectors:["aside#cookies,.overlay-cookies"],detectCmp:[{exists:"#cookies .cookies-btn"}],detectPopup:[{visible:"#cookies #submitCookies"}],optIn:[{waitForThenClick:"#cookies #submitCookies"}],optOut:[{waitForThenClick:"#cookies #rejectCookies"}]},{name:"Adroll",prehideSelectors:["#adroll_consent_container"],detectCmp:[{exists:"#adroll_consent_container"}],detectPopup:[{visible:"#adroll_consent_container"}],optIn:[{waitForThenClick:"#adroll_consent_accept"}],optOut:[{waitForThenClick:"#adroll_consent_reject"}],test:[{eval:"EVAL_ADROLL_0"}]},{name:"affinity.serif.com",detectCmp:[{exists:".c-cookie-banner button[data-qa='allow-all-cookies']"}],detectPopup:[{visible:".c-cookie-banner"}],optIn:[{click:'button[data-qa="allow-all-cookies"]'}],optOut:[{click:'button[data-qa="manage-cookies"]'},{waitFor:'.c-cookie-banner ~ [role="dialog"]'},{waitForThenClick:'.c-cookie-banner ~ [role="dialog"] input[type="checkbox"][value="true"]',all:!0},{click:'.c-cookie-banner ~ [role="dialog"] .c-modal__action button'}],test:[{wait:500},{eval:"EVAL_AFFINITY_SERIF_COM_0"}]},{name:"agolde.com",cosmetic:!0,prehideSelectors:["#modal-1 div[data-micromodal-close]"],detectCmp:[{exists:"#modal-1 div[aria-labelledby=modal-1-title]"}],detectPopup:[{exists:"#modal-1 div[data-micromodal-close]"}],optIn:[{click:'button[aria-label="Close modal"]'}],optOut:[{hide:"#modal-1 div[data-micromodal-close]"}]},{name:"aliexpress",vendorUrl:"https://aliexpress.com/",runContext:{urlPattern:"^https://.*\\.aliexpress\\.com/"},prehideSelectors:["#gdpr-new-container"],detectCmp:[{exists:"#gdpr-new-container"}],detectPopup:[{visible:"#gdpr-new-container"}],optIn:[{waitForThenClick:"#gdpr-new-container .btn-accept"}],optOut:[{waitForThenClick:"#gdpr-new-container .btn-more"},{waitFor:"#gdpr-new-container .gdpr-dialog-switcher"},{click:"#gdpr-new-container .switcher-on",all:!0,optional:!0},{click:"#gdpr-new-container .btn-save"}]},{name:"almacmp",prehideSelectors:["#alma-cmpv2-container"],detectCmp:[{exists:"#alma-cmpv2-container"}],detectPopup:[{visible:"#alma-cmpv2-container #almacmp-modal-layer1"}],optIn:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalConfirmBtn"}],optOut:[{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer1 #almacmp-modalSettingBtn"},{waitFor:"#alma-cmpv2-container #almacmp-modal-layer2"},{waitForThenClick:"#alma-cmpv2-container #almacmp-modal-layer2 #almacmp-reject-all-layer2"}],test:[{eval:"EVAL_ALMACMP_0"}]},{name:"altium.com",cosmetic:!0,prehideSelectors:[".altium-privacy-bar"],detectCmp:[{exists:".altium-privacy-bar"}],detectPopup:[{exists:".altium-privacy-bar"}],optIn:[{click:"a.altium-privacy-bar__btn"}],optOut:[{hide:".altium-privacy-bar"}]},{name:"amazon.com",prehideSelectors:['span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'],detectCmp:[{exists:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],detectPopup:[{visible:'span[data-action="sp-cc"][data-sp-cc*="rejectAllAction"]'}],optIn:[{waitForVisible:"#sp-cc-accept"},{wait:500},{click:"#sp-cc-accept"}],optOut:[{waitForVisible:"#sp-cc-rejectall-link"},{wait:500},{click:"#sp-cc-rejectall-link"}]},{name:"aquasana.com",cosmetic:!0,prehideSelectors:["#consent-tracking"],detectCmp:[{exists:"#consent-tracking"}],detectPopup:[{exists:"#consent-tracking"}],optIn:[{click:"#accept_consent"}],optOut:[{hide:"#consent-tracking"}]},{name:"arbeitsagentur",vendorUrl:"https://www.arbeitsagentur.de/",prehideSelectors:[".modal-open bahf-cookie-disclaimer-dpl3"],detectCmp:[{exists:"bahf-cookie-disclaimer-dpl3"}],detectPopup:[{visible:"bahf-cookie-disclaimer-dpl3"}],optIn:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-primary"]}],optOut:[{waitForThenClick:["bahf-cookie-disclaimer-dpl3","bahf-cd-modal-dpl3 .ba-btn-contrast"]}],test:[{eval:"EVAL_ARBEITSAGENTUR_TEST"}]},{name:"asus",vendorUrl:"https://www.asus.com/",runContext:{urlPattern:"^https://www\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info,#cookie-policy-info-bg"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{waitForThenClick:'#cookie-policy-info [data-agree="Accept Cookies"]'}],optOut:[{if:{exists:"#cookie-policy-info .btn-reject"},then:[{waitForThenClick:"#cookie-policy-info .btn-reject"}],else:[{waitForThenClick:"#cookie-policy-info .btn-setting"},{waitForThenClick:'#cookie-policy-lightbox-wrapper [data-agree="Save Settings"]'}]}]},{name:"athlinks-com",runContext:{urlPattern:"^https://(www\\.)?athlinks\\.com/"},cosmetic:!0,prehideSelectors:["#footer-container ~ div"],detectCmp:[{exists:"#footer-container ~ div"}],detectPopup:[{visible:"#footer-container > div"}],optIn:[{click:"#footer-container ~ div button"}],optOut:[{hide:"#footer-container ~ div"}]},{name:"ausopen.com",cosmetic:!0,detectCmp:[{exists:".gdpr-popup__message"}],detectPopup:[{visible:".gdpr-popup__message"}],optOut:[{hide:".gdpr-popup__message"}],optIn:[{click:".gdpr-popup__message button"}]},{name:"automattic-cmp-optout",prehideSelectors:['form[class*="cookie-banner"][method="post"]'],detectCmp:[{exists:'form[class*="cookie-banner"][method="post"]'}],detectPopup:[{visible:'form[class*="cookie-banner"][method="post"]'}],optIn:[{click:'a[class*="accept-all-button"]'}],optOut:[{click:'form[class*="cookie-banner"] div[class*="simple-options"] a[class*="customize-button"]'},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:'a[class*="accept-selection-button"]'}]},{name:"aws.amazon.com",prehideSelectors:["#awsccc-cb-content","#awsccc-cs-container","#awsccc-cs-modalOverlay","#awsccc-cs-container-inner"],detectCmp:[{exists:"#awsccc-cb-content"}],detectPopup:[{visible:"#awsccc-cb-content"}],optIn:[{click:"button[data-id=awsccc-cb-btn-accept"}],optOut:[{click:"button[data-id=awsccc-cb-btn-customize]"},{waitFor:"input[aria-checked]"},{click:"input[aria-checked=true]",all:!0,optional:!0},{click:"button[data-id=awsccc-cs-btn-save]"}]},{name:"axeptio",prehideSelectors:[".axeptio_widget"],detectCmp:[{exists:".axeptio_widget"}],detectPopup:[{visible:".axeptio_widget"}],optIn:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_acceptAll"}],optOut:[{waitFor:".axeptio-widget--open"},{click:"button#axeptio_btn_dismiss"}],test:[{eval:"EVAL_AXEPTIO_0"}]},{name:"baden-wuerttemberg.de",prehideSelectors:[".cookie-alert.t-dark"],cosmetic:!0,detectCmp:[{exists:".cookie-alert.t-dark"}],detectPopup:[{visible:".cookie-alert.t-dark"}],optIn:[{click:".cookie-alert__form input:not([disabled]):not([checked])"},{click:".cookie-alert__button button"}],optOut:[{hide:".cookie-alert.t-dark"}]},{name:"bahn-de",vendorUrl:"https://www.bahn.de/",cosmetic:!1,runContext:{main:!0,frame:!1,urlPattern:"^https://(www\\.)?bahn\\.de/"},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:["body > div:first-child","#consent-layer"]}],detectPopup:[{visible:["body > div:first-child","#consent-layer"]}],optIn:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-all-cookies"]}],optOut:[{waitForThenClick:["body > div:first-child","#consent-layer .js-accept-essential-cookies"]}],test:[{eval:"EVAL_BAHN_TEST"}]},{name:"bbb.org",runContext:{urlPattern:"^https://www\\.bbb\\.org/"},cosmetic:!0,prehideSelectors:['div[aria-label="use of cookies on bbb.org"]'],detectCmp:[{exists:'div[aria-label="use of cookies on bbb.org"]'}],detectPopup:[{visible:'div[aria-label="use of cookies on bbb.org"]'}],optIn:[{click:'div[aria-label="use of cookies on bbb.org"] button.bds-button-unstyled span.visually-hidden'}],optOut:[{hide:'div[aria-label="use of cookies on bbb.org"]'}]},{name:"bing.com",prehideSelectors:["#bnp_container"],detectCmp:[{exists:"#bnp_cookie_banner"}],detectPopup:[{visible:"#bnp_cookie_banner"}],optIn:[{click:"#bnp_btn_accept"}],optOut:[{click:"#bnp_btn_preference"},{click:"#mcp_savesettings"}],test:[{eval:"EVAL_BING_0"}]},{name:"blocksy",vendorUrl:"https://creativethemes.com/blocksy/docs/extensions/cookies-consent/",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[".cookie-notification"],detectCmp:[{exists:"#blocksy-ext-cookies-consent-styles-css"}],detectPopup:[{visible:".cookie-notification"}],optIn:[{click:".cookie-notification .ct-cookies-decline-button"}],optOut:[{waitForThenClick:".cookie-notification .ct-cookies-decline-button"}],test:[{eval:"EVAL_BLOCKSY_0"}]},{name:"borlabs",detectCmp:[{exists:"._brlbs-block-content"}],detectPopup:[{visible:"._brlbs-bar-wrap,._brlbs-box-wrap"}],optIn:[{click:"a[data-cookie-accept-all]"}],optOut:[{click:"a[data-cookie-individual]"},{waitForVisible:".cookie-preference"},{click:"input[data-borlabs-cookie-checkbox]:checked",all:!0,optional:!0},{click:"#CookiePrefSave"},{wait:500}],prehideSelectors:["#BorlabsCookieBox"],test:[{eval:"EVAL_BORLABS_0"}]},{name:"bundesregierung.de",prehideSelectors:[".bpa-cookie-banner"],detectCmp:[{exists:".bpa-cookie-banner"}],detectPopup:[{visible:".bpa-cookie-banner .bpa-module-full-hero"}],optIn:[{click:".bpa-accept-all-button"}],optOut:[{wait:500,comment:"click is not immediately recognized"},{waitForThenClick:".bpa-close-button"}],test:[{eval:"EVAL_BUNDESREGIERUNG_DE_0"}]},{name:"burpee.com",cosmetic:!0,prehideSelectors:["#notice-cookie-block"],detectCmp:[{exists:"#notice-cookie-block"}],detectPopup:[{exists:"#html-body #notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{hide:"#html-body #notice-cookie-block, #notice-cookie"}]},{name:"canva.com",prehideSelectors:['div[role="dialog"] a[data-anchor-id="cookie-policy"]'],detectCmp:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],detectPopup:[{exists:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'}],optIn:[{click:'div[role="dialog"] button:nth-child(1)'}],optOut:[{if:{exists:'div[role="dialog"] button:nth-child(3)'},then:[{click:'div[role="dialog"] button:nth-child(2)'}],else:[{click:'div[role="dialog"] button:nth-child(2)'},{waitFor:'div[role="dialog"] a[data-anchor-id="cookie-policy"]'},{waitFor:'div[role="dialog"] button[role=switch]'},{click:'div[role="dialog"] button:nth-child(2):not([role])'},{click:'div[role="dialog"] div:last-child button:only-child'}]}],test:[{eval:"EVAL_CANVA_0"}]},{name:"canyon.com",runContext:{urlPattern:"^https://www\\.canyon\\.com/"},prehideSelectors:["div.modal.cookiesModal.is-open"],detectCmp:[{exists:"div.modal.cookiesModal.is-open"}],detectPopup:[{visible:"div.modal.cookiesModal.is-open"}],optIn:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-submit"]'}],optOut:[{click:'div.cookiesModal__buttonWrapper > button[data-closecause="close-by-manage-cookies"]'},{waitForThenClick:"button#js-manage-data-privacy-save-button"}]},{name:"cc-banner-springer",prehideSelectors:[".cc-banner[data-cc-banner]"],detectCmp:[{exists:".cc-banner[data-cc-banner]"}],detectPopup:[{visible:".cc-banner[data-cc-banner]"}],optIn:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=accept]"}],optOut:[{if:{exists:".cc-banner[data-cc-banner] button[data-cc-action=reject]"},then:[{click:".cc-banner[data-cc-banner] button[data-cc-action=reject]"}],else:[{waitForThenClick:".cc-banner[data-cc-banner] button[data-cc-action=preferences]"},{waitFor:".cc-preferences[data-cc-preferences]"},{click:".cc-preferences[data-cc-preferences] input[type=radio][data-cc-action=toggle-category][value=off]",all:!0,optional:!0},{if:{exists:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"},then:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=reject]"}],else:[{click:".cc-preferences[data-cc-preferences] button[data-cc-action=save]"}]}]}],test:[{eval:"EVAL_CC_BANNER2_0"}]},{name:"cc_banner",cosmetic:!0,prehideSelectors:[".cc_banner-wrapper"],detectCmp:[{exists:".cc_banner-wrapper"}],detectPopup:[{visible:".cc_banner"}],optIn:[{click:".cc_btn_accept_all"}],optOut:[{hide:".cc_banner-wrapper"}]},{name:"ciaopeople.it",prehideSelectors:["#cp-gdpr-choices"],detectCmp:[{exists:"#cp-gdpr-choices"}],detectPopup:[{visible:"#cp-gdpr-choices"}],optIn:[{waitForThenClick:".gdpr-btm__right > button:nth-child(2)"}],optOut:[{waitForThenClick:".gdpr-top-content > button"},{waitFor:".gdpr-top-back"},{waitForThenClick:".gdpr-btm__right > button:nth-child(1)"}],test:[{visible:"#cp-gdpr-choices",check:"none"}]},{vendorUrl:"https://www.civicuk.com/cookie-control/",name:"civic-cookie-control",prehideSelectors:["#ccc-module,#ccc-overlay"],detectCmp:[{exists:"#ccc-module"}],detectPopup:[{visible:"#ccc"},{visible:"#ccc-module"}],optOut:[{click:"#ccc-reject-settings"}],optIn:[{click:"#ccc-recommended-settings"}]},{name:"click.io",prehideSelectors:["#cl-consent"],detectCmp:[{exists:"#cl-consent"}],detectPopup:[{visible:"#cl-consent"}],optIn:[{waitForThenClick:'#cl-consent [data-role="b_agree"]'}],optOut:[{waitFor:'#cl-consent [data-role="b_options"]'},{wait:500},{click:'#cl-consent [data-role="b_options"]'},{waitFor:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]'},{click:'.cl-consent-popup.cl-consent-visible [data-role="alloff"]',all:!0},{click:'[data-role="b_save"]'}],test:[{eval:"EVAL_CLICKIO_0",comment:"TODO: this only checks if we interacted at all"}]},{name:"clinch",intermediate:!1,runContext:{frame:!1,main:!0},prehideSelectors:[".consent-modal[role=dialog]"],detectCmp:[{exists:".consent-modal[role=dialog]"}],detectPopup:[{visible:".consent-modal[role=dialog]"}],optIn:[{click:"#consent_agree"}],optOut:[{if:{exists:"#consent_reject"},then:[{click:"#consent_reject"}],else:[{click:"#manage_cookie_preferences"},{click:"#cookie_consent_preferences input:checked",all:!0,optional:!0},{click:"#consent_save"}]}],test:[{eval:"EVAL_CLINCH_0"}]},{name:"clustrmaps.com",runContext:{urlPattern:"^https://(www\\.)?clustrmaps\\.com/"},cosmetic:!0,prehideSelectors:["#gdpr-cookie-message"],detectCmp:[{exists:"#gdpr-cookie-message"}],detectPopup:[{visible:"#gdpr-cookie-message"}],optIn:[{click:"button#gdpr-cookie-accept"}],optOut:[{hide:"#gdpr-cookie-message"}]},{name:"coinbase",intermediate:!1,runContext:{frame:!0,main:!0,urlPattern:"^https://(www|help)\\.coinbase\\.com"},prehideSelectors:[],detectCmp:[{exists:"div[class^=CookieBannerContent__Container]"}],detectPopup:[{visible:"div[class^=CookieBannerContent__Container]"}],optIn:[{click:"div[class^=CookieBannerContent__CTA] :nth-last-child(1)"}],optOut:[{click:"button[class^=CookieBannerContent__Settings]"},{click:"div[class^=CookiePreferencesModal__CategoryContainer] input:checked",all:!0,optional:!0},{click:"div[class^=CookiePreferencesModal__ButtonContainer] > button"}],test:[{eval:"EVAL_COINBASE_0"}]},{name:"Complianz banner",prehideSelectors:["#cmplz-cookiebanner-container"],detectCmp:[{exists:"#cmplz-cookiebanner-container .cmplz-cookiebanner"}],detectPopup:[{visible:"#cmplz-cookiebanner-container .cmplz-cookiebanner",check:"any"}],optIn:[{waitForThenClick:".cmplz-cookiebanner .cmplz-accept"}],optOut:[{waitForThenClick:".cmplz-cookiebanner .cmplz-deny"}],test:[{eval:"EVAL_COMPLIANZ_BANNER_0"}]},{name:"Complianz categories",prehideSelectors:['.cc-type-categories[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-categories[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{exists:'.cc-type-categories[aria-describedby="cookieconsent:desc"] .cc-dismiss'},then:[{click:".cc-dismiss"}],else:[{click:".cc-type-categories input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-save"}]}]},{name:"Complianz notice",prehideSelectors:['.cc-type-info[aria-describedby="cookieconsent:desc"]'],cosmetic:!0,detectCmp:[{exists:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],detectPopup:[{visible:'.cc-type-info[aria-describedby="cookieconsent:desc"] .cc-compliance .cc-btn'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{if:{exists:".cc-deny"},then:[{click:".cc-deny"}],else:[{hide:'[aria-describedby="cookieconsent:desc"]'}]}]},{name:"Complianz opt-both",prehideSelectors:['[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'],detectCmp:[{exists:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],detectPopup:[{visible:'[aria-describedby="cookieconsent:desc"] .cc-type-opt-both'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{waitForThenClick:".cc-deny"}]},{name:"Complianz opt-out",prehideSelectors:['[aria-describedby="cookieconsent:desc"].cc-type-opt-out'],detectCmp:[{exists:'[aria-describedby="cookieconsent:desc"].cc-type-opt-out'}],detectPopup:[{visible:'[aria-describedby="cookieconsent:desc"].cc-type-opt-out'}],optIn:[{click:".cc-accept-all",optional:!0},{click:".cc-allow",optional:!0},{click:".cc-dismiss",optional:!0}],optOut:[{if:{exists:".cc-deny"},then:[{click:".cc-deny"}],else:[{hide:'[aria-describedby="cookieconsent:desc"]'}]}]},{name:"Complianz optin",prehideSelectors:['.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'],detectCmp:[{exists:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],detectPopup:[{visible:'.cc-type-opt-in[aria-describedby="cookieconsent:desc"]'}],optIn:[{any:[{click:".cc-accept-all"},{click:".cc-allow"},{click:".cc-dismiss"}]}],optOut:[{if:{visible:".cc-deny"},then:[{click:".cc-deny"}],else:[{if:{visible:".cc-settings"},then:[{waitForThenClick:".cc-settings"},{waitForVisible:".cc-settings-view"},{click:".cc-settings-view input[type=checkbox]:not([disabled]):checked",all:!0,optional:!0},{click:".cc-settings-view .cc-btn-accept-selected"}],else:[{click:".cc-dismiss"}]}]}]},{name:"cookie-law-info",prehideSelectors:["#cookie-law-info-bar"],detectCmp:[{exists:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_DETECT"}],detectPopup:[{visible:"#cookie-law-info-bar"}],optIn:[{click:'[data-cli_action="accept_all"]'}],optOut:[{hide:"#cookie-law-info-bar"},{eval:"EVAL_COOKIE_LAW_INFO_0"}],test:[{eval:"EVAL_COOKIE_LAW_INFO_1"}]},{name:"cookie-manager-popup",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,detectCmp:[{exists:"#notice-cookie-block #allow-functional-cookies, #notice-cookie-block #btn-cookie-settings"}],detectPopup:[{visible:"#notice-cookie-block"}],optIn:[{click:"#btn-cookie-allow"}],optOut:[{if:{exists:"#allow-functional-cookies"},then:[{click:"#allow-functional-cookies"}],else:[{waitForThenClick:"#btn-cookie-settings"},{waitForVisible:".modal-body"},{click:'.modal-body input:checked, .switch[data-switch="on"]',all:!0,optional:!0},{click:'[role="dialog"] .modal-footer button'}]}],prehideSelectors:["#btn-cookie-settings"],test:[{eval:"EVAL_COOKIE_MANAGER_POPUP_0"}]},{name:"cookie-notice",prehideSelectors:["#cookie-notice"],cosmetic:!0,detectCmp:[{visible:"#cookie-notice .cookie-notice-container"}],detectPopup:[{visible:"#cookie-notice"}],optIn:[{click:"#cn-accept-cookie"}],optOut:[{hide:"#cookie-notice"}]},{name:"cookie-script",vendorUrl:"https://cookie-script.com/",prehideSelectors:["#cookiescript_injected"],detectCmp:[{exists:"#cookiescript_injected"}],detectPopup:[{visible:"#cookiescript_injected"}],optOut:[{if:{exists:"#cookiescript_reject"},then:[{wait:100},{click:"#cookiescript_reject"}],else:[{click:"#cookiescript_manage"},{waitForVisible:".cookiescript_fsd_main"},{waitForThenClick:"#cookiescript_reject"}]}],optIn:[{click:"#cookiescript_accept"}]},{name:"cookieacceptbar",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#cookieAcceptBar.cookieAcceptBar"],detectCmp:[{exists:"#cookieAcceptBar.cookieAcceptBar"}],detectPopup:[{visible:"#cookieAcceptBar.cookieAcceptBar"}],optIn:[{waitForThenClick:"#cookieAcceptBarConfirm"}],optOut:[{hide:"#cookieAcceptBar.cookieAcceptBar"}]},{name:"cookiealert",intermediate:!1,prehideSelectors:[],runContext:{frame:!0,main:!0},detectCmp:[{exists:".cookie-alert-extended"}],detectPopup:[{visible:".cookie-alert-extended-modal"}],optIn:[{click:"button[data-controller='cookie-alert/extended/button/accept']"},{eval:"EVAL_COOKIEALERT_0"}],optOut:[{click:"a[data-controller='cookie-alert/extended/detail-link']"},{click:".cookie-alert-configuration-input:checked",all:!0,optional:!0},{click:"button[data-controller='cookie-alert/extended/button/configuration']"},{eval:"EVAL_COOKIEALERT_0"}],test:[{eval:"EVAL_COOKIEALERT_2"}]},{name:"cookieconsent2",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v2.x.x of the library",prehideSelectors:["#cc--main"],detectCmp:[{exists:"#cc--main"}],detectPopup:[{visible:"#cm"},{exists:"#s-all-bn"}],optIn:[{waitForThenClick:"#s-all-bn"}],optOut:[{waitForThenClick:"#s-rall-bn"}],test:[{eval:"EVAL_COOKIECONSENT2_TEST"}]},{name:"cookieconsent3",vendorUrl:"https://www.github.com/orestbida/cookieconsent",comment:"supports v3.x.x of the library",prehideSelectors:["#cc-main"],detectCmp:[{exists:"#cc-main"}],detectPopup:[{visible:"#cc-main .cm-wrapper"}],optIn:[{waitForThenClick:".cm__btn[data-role=all]"}],optOut:[{waitForThenClick:".cm__btn[data-role=necessary]"}],test:[{eval:"EVAL_COOKIECONSENT3_TEST"}]},{name:"cookiefirst.com",prehideSelectors:["#cookiefirst-root,.cookiefirst-root,[aria-labelledby=cookie-preference-panel-title]"],detectCmp:[{exists:"#cookiefirst-root,.cookiefirst-root"}],detectPopup:[{visible:"#cookiefirst-root,.cookiefirst-root"}],optIn:[{click:"button[data-cookiefirst-action=accept]"}],optOut:[{if:{exists:"button[data-cookiefirst-action=adjust]"},then:[{click:"button[data-cookiefirst-action=adjust]"},{waitForVisible:"[data-cookiefirst-widget=modal]",timeout:1e3},{eval:"EVAL_COOKIEFIRST_1"},{wait:1e3},{click:"button[data-cookiefirst-action=save]"}],else:[{click:"button[data-cookiefirst-action=reject]"}]}],test:[{eval:"EVAL_COOKIEFIRST_0"}]},{name:"Cookie Information Banner",prehideSelectors:["#cookie-information-template-wrapper"],detectCmp:[{exists:"#cookie-information-template-wrapper"}],detectPopup:[{visible:"#cookie-information-template-wrapper"}],optIn:[{eval:"EVAL_COOKIEINFORMATION_1"}],optOut:[{hide:"#cookie-information-template-wrapper",comment:"some templates don't hide the banner automatically"},{eval:"EVAL_COOKIEINFORMATION_0"}],test:[{eval:"EVAL_COOKIEINFORMATION_2"}]},{name:"cookieyes",prehideSelectors:[".cky-overlay,.cky-consent-container"],detectCmp:[{exists:".cky-consent-container"}],detectPopup:[{visible:".cky-consent-container"}],optIn:[{waitForThenClick:".cky-consent-container [data-cky-tag=accept-button]"}],optOut:[{if:{exists:".cky-consent-container [data-cky-tag=reject-button]"},then:[{waitForThenClick:".cky-consent-container [data-cky-tag=reject-button]"}],else:[{if:{exists:".cky-consent-container [data-cky-tag=settings-button]"},then:[{click:".cky-consent-container [data-cky-tag=settings-button]"},{waitFor:".cky-modal-open input[type=checkbox]"},{click:".cky-modal-open input[type=checkbox]:checked",all:!0,optional:!0},{waitForThenClick:".cky-modal [data-cky-tag=detail-save-button]"}],else:[{hide:".cky-consent-container,.cky-overlay"}]}]}],test:[{eval:"EVAL_COOKIEYES_0"}]},{name:"corona-in-zahlen.de",prehideSelectors:[".cookiealert"],detectCmp:[{exists:".cookiealert"}],detectPopup:[{visible:".cookiealert"}],optOut:[{click:".configurecookies"},{click:".confirmcookies"}],optIn:[{click:".acceptcookies"}]},{name:"crossfit-com",cosmetic:!0,prehideSelectors:['body #modal > div > div[class^="_wrapper_"]'],detectCmp:[{exists:'body #modal > div > div[class^="_wrapper_"]'}],detectPopup:[{visible:'body #modal > div > div[class^="_wrapper_"]'}],optIn:[{click:'button[aria-label="accept cookie policy"]'}],optOut:[{hide:'body #modal > div > div[class^="_wrapper_"]'}]},{name:"csu-landtag-de",runContext:{urlPattern:"^https://(www\\.|)?csu-landtag\\.de"},prehideSelectors:["#cookie-disclaimer"],detectCmp:[{exists:"#cookie-disclaimer"}],detectPopup:[{visible:"#cookie-disclaimer"}],optIn:[{click:"#cookieall"}],optOut:[{click:"#cookiesel"}]},{name:"dailymotion-us",cosmetic:!0,prehideSelectors:['div[class*="CookiePopup__desktopContainer"]:has(div[class*="CookiePopup"])'],detectCmp:[{exists:'div[class*="CookiePopup__desktopContainer"]'}],detectPopup:[{visible:'div[class*="CookiePopup__desktopContainer"]'}],optIn:[{click:'div[class*="CookiePopup__desktopContainer"] > button > span'}],optOut:[{hide:'div[class*="CookiePopup__desktopContainer"]'}]},{name:"dailymotion.com",runContext:{urlPattern:"^https://(www\\.)?dailymotion\\.com/"},prehideSelectors:['div[class*="Overlay__container"]:has(div[class*="TCF2Popup"])'],detectCmp:[{exists:'div[class*="TCF2Popup"]'}],detectPopup:[{visible:'[class*="TCF2Popup"] a[href^="https://www.dailymotion.com/legal/cookiemanagement"]'}],optIn:[{waitForThenClick:'button[class*="TCF2Popup__button"]:not([class*="TCF2Popup__personalize"])'}],optOut:[{waitForThenClick:'button[class*="TCF2ContinueWithoutAcceptingButton"]'}],test:[{eval:"EVAL_DAILYMOTION_0"}]},{name:"deepl.com",prehideSelectors:[".dl_cookieBanner_container"],detectCmp:[{exists:".dl_cookieBanner_container"}],detectPopup:[{visible:".dl_cookieBanner_container"}],optOut:[{click:".dl_cookieBanner--buttonSelected"}],optIn:[{click:".dl_cookieBanner--buttonAll"}]},{name:"delta.com",runContext:{urlPattern:"^https://www\\.delta\\.com/"},cosmetic:!0,prehideSelectors:["ngc-cookie-banner"],detectCmp:[{exists:"div.cookie-footer-container"}],detectPopup:[{visible:"div.cookie-footer-container"}],optIn:[{click:" button.cookie-close-icon"}],optOut:[{hide:"div.cookie-footer-container"}]},{name:"dmgmedia-us",prehideSelectors:["#mol-ads-cmp-iframe, div.mol-ads-cmp > form > div"],detectCmp:[{exists:"div.mol-ads-cmp > form > div"}],detectPopup:[{waitForVisible:"div.mol-ads-cmp > form > div"}],optIn:[{waitForThenClick:"button.mol-ads-cmp--btn-primary"}],optOut:[{waitForThenClick:"div.mol-ads-ccpa--message > u > a"},{waitForVisible:".mol-ads-cmp--modal-dialog"},{waitForThenClick:"a.mol-ads-cmp-footer-privacy"},{waitForThenClick:"button.mol-ads-cmp--btn-secondary"}]},{name:"dmgmedia",prehideSelectors:['[data-project="mol-fe-cmp"]'],detectCmp:[{exists:'[data-project="mol-fe-cmp"]'}],detectPopup:[{visible:'[data-project="mol-fe-cmp"]'}],optIn:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=primary]'}],optOut:[{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=basic]'},{waitForVisible:'[data-project="mol-fe-cmp"] div[class*="tabContent"]'},{waitForThenClick:'[data-project="mol-fe-cmp"] div[class*="toggle"][class*="enabled"]',all:!0},{waitForThenClick:'[data-project="mol-fe-cmp"] button[class*=white]'}]},{name:"dndbeyond",vendorUrl:"https://www.dndbeyond.com/",runContext:{urlPattern:"^https://(www\\.)?dndbeyond\\.com/"},prehideSelectors:["[id^=cookie-consent-banner]"],detectCmp:[{exists:"[id^=cookie-consent-banner]"}],detectPopup:[{visible:"[id^=cookie-consent-banner]"}],optIn:[{waitForThenClick:"#cookie-consent-granted"}],optOut:[{waitForThenClick:"#cookie-consent-denied"}],test:[{eval:"EVAL_DNDBEYOND_TEST"}]},{name:"Drupal",detectCmp:[{exists:"#drupalorg-crosssite-gdpr"}],detectPopup:[{visible:"#drupalorg-crosssite-gdpr"}],optOut:[{click:".no"}],optIn:[{click:".yes"}]},{name:"WP DSGVO Tools",link:"https://wordpress.org/plugins/shapepress-dsgvo/",prehideSelectors:[".sp-dsgvo"],cosmetic:!0,detectCmp:[{exists:".sp-dsgvo.sp-dsgvo-popup-overlay"}],detectPopup:[{visible:".sp-dsgvo.sp-dsgvo-popup-overlay",check:"any"}],optIn:[{click:".sp-dsgvo-privacy-btn-accept-all",all:!0}],optOut:[{hide:".sp-dsgvo.sp-dsgvo-popup-overlay"}],test:[{eval:"EVAL_DSGVO_0"}]},{name:"dunelm.com",prehideSelectors:["div[data-testid=cookie-consent-modal-backdrop]"],detectCmp:[{exists:"div[data-testid=cookie-consent-message-contents]"}],detectPopup:[{visible:"div[data-testid=cookie-consent-message-contents]"}],optIn:[{click:'[data-testid="cookie-consent-allow-all"]'}],optOut:[{click:"button[data-testid=cookie-consent-adjust-settings]"},{click:"button[data-testid=cookie-consent-preferences-save]"}],test:[{eval:"EVAL_DUNELM_0"}]},{name:"ecosia",vendorUrl:"https://www.ecosia.org/",runContext:{urlPattern:"^https://www\\.ecosia\\.org/"},prehideSelectors:[".cookie-wrapper"],detectCmp:[{exists:".cookie-wrapper > .cookie-notice"}],detectPopup:[{visible:".cookie-wrapper > .cookie-notice"}],optIn:[{waitForThenClick:"[data-test-id=cookie-notice-accept]"}],optOut:[{waitForThenClick:"[data-test-id=cookie-notice-reject]"}]},{name:"Ensighten ensModal",prehideSelectors:[".ensModal"],detectCmp:[{exists:".ensModal"}],detectPopup:[{visible:".ensModal"}],optIn:[{waitForThenClick:"#modalAcceptButton"}],optOut:[{waitForThenClick:".ensCheckbox:checked",all:!0},{waitForThenClick:"#ensSave"}]},{name:"Ensighten ensNotifyBanner",prehideSelectors:["#ensNotifyBanner"],detectCmp:[{exists:"#ensNotifyBanner"}],detectPopup:[{visible:"#ensNotifyBanner"}],optIn:[{waitForThenClick:"#ensCloseBanner"}],optOut:[{waitForThenClick:"#ensRejectAll,#rejectAll,#ensRejectBanner"}]},{name:"etsy",prehideSelectors:["#gdpr-single-choice-overlay","#gdpr-privacy-settings"],detectCmp:[{exists:"#gdpr-single-choice-overlay"}],detectPopup:[{visible:"#gdpr-single-choice-overlay"}],optOut:[{click:"button[data-gdpr-open-full-settings]"},{waitForVisible:".gdpr-overlay-body input",timeout:3e3},{wait:1e3},{eval:"EVAL_ETSY_0"},{eval:"EVAL_ETSY_1"}],optIn:[{click:"button[data-gdpr-single-choice-accept]"}]},{name:"eu-cookie-compliance-banner",detectCmp:[{exists:"body.eu-cookie-compliance-popup-open"}],detectPopup:[{exists:"body.eu-cookie-compliance-popup-open"}],optIn:[{click:".agree-button"}],optOut:[{if:{visible:".decline-button,.eu-cookie-compliance-save-preferences-button"},then:[{click:".decline-button,.eu-cookie-compliance-save-preferences-button"}]},{hide:".eu-cookie-compliance-banner-info, #sliding-popup"}],test:[{eval:"EVAL_EU_COOKIE_COMPLIANCE_0"}]},{name:"EU Cookie Law",prehideSelectors:[".pea_cook_wrapper,.pea_cook_more_info_popover"],cosmetic:!0,detectCmp:[{exists:".pea_cook_wrapper"}],detectPopup:[{wait:500},{visible:".pea_cook_wrapper"}],optIn:[{click:"#pea_cook_btn"}],optOut:[{hide:".pea_cook_wrapper"}],test:[{eval:"EVAL_EU_COOKIE_LAW_0"}]},{name:"europa-eu",vendorUrl:"https://ec.europa.eu/",runContext:{urlPattern:"^https://[^/]*europa\\.eu/"},prehideSelectors:["#cookie-consent-banner"],detectCmp:[{exists:".cck-container"}],detectPopup:[{visible:".cck-container"}],optIn:[{waitForThenClick:'.cck-actions-button[href="#accept"]'}],optOut:[{waitForThenClick:'.cck-actions-button[href="#refuse"]',hide:".cck-container"}]},{name:"EZoic",prehideSelectors:["#ez-cookie-dialog-wrapper"],detectCmp:[{exists:"#ez-cookie-dialog-wrapper"}],detectPopup:[{visible:"#ez-cookie-dialog-wrapper"}],optIn:[{click:"#ez-accept-all",optional:!0},{eval:"EVAL_EZOIC_0",optional:!0}],optOut:[{wait:500},{click:"#ez-manage-settings"},{waitFor:"#ez-cookie-dialog input[type=checkbox]"},{click:"#ez-cookie-dialog input[type=checkbox]:checked",all:!0},{click:"#ez-save-settings"}],test:[{eval:"EVAL_EZOIC_1"}]},{name:"facebook",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?facebook\\.com/"},prehideSelectors:['div[data-testid="cookie-policy-manage-dialog"]'],detectCmp:[{exists:'div[data-testid="cookie-policy-manage-dialog"]'}],detectPopup:[{visible:'div[data-testid="cookie-policy-manage-dialog"]'}],optIn:[{waitForThenClick:'button[data-cookiebanner="accept_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}],optOut:[{waitForThenClick:'button[data-cookiebanner="accept_only_essential_button"]'},{waitForVisible:'div[data-testid="cookie-policy-manage-dialog"]',check:"none"}]},{name:"fides",vendorUrl:"https://github.com/ethyca/fides",prehideSelectors:["#fides-overlay"],detectCmp:[{exists:"#fides-overlay #fides-banner"}],detectPopup:[{visible:"#fides-overlay #fides-banner"}],optIn:[{waitForThenClick:'#fides-banner [data-testid="Accept all-btn"]'}],optOut:[{waitForThenClick:'#fides-banner [data-testid="Reject all-btn"]'}]},{name:"funding-choices",prehideSelectors:[".fc-consent-root,.fc-dialog-container,.fc-dialog-overlay,.fc-dialog-content"],detectCmp:[{exists:".fc-consent-root"}],detectPopup:[{exists:".fc-dialog-container"}],optOut:[{click:".fc-cta-do-not-consent,.fc-cta-manage-options"},{click:".fc-preference-consent:checked,.fc-preference-legitimate-interest:checked",all:!0,optional:!0},{click:".fc-confirm-choices",optional:!0}],optIn:[{click:".fc-cta-consent"}]},{name:"geeks-for-geeks",runContext:{urlPattern:"^https://www\\.geeksforgeeks\\.org/"},cosmetic:!0,prehideSelectors:[".cookie-consent"],detectCmp:[{exists:".cookie-consent"}],detectPopup:[{visible:".cookie-consent"}],optIn:[{click:".cookie-consent button.consent-btn"}],optOut:[{hide:".cookie-consent"}]},{name:"generic-cosmetic",cosmetic:!0,prehideSelectors:["#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"],detectCmp:[{exists:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],detectPopup:[{visible:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}],optIn:[],optOut:[{hide:"#js-cookie-banner,.js-cookie-banner,.cookie-banner,#cookie-banner"}]},{name:"google-consent-standalone",prehideSelectors:[],detectCmp:[{exists:'a[href^="https://policies.google.com/technologies/cookies"'},{exists:'form[action^="https://consent.google."][action$=".com/save"]'}],detectPopup:[{visible:'a[href^="https://policies.google.com/technologies/cookies"'}],optIn:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=false]) button'}],optOut:[{waitForThenClick:'form[action^="https://consent.google."][action$=".com/save"]:has(input[name=set_eom][value=true]) button'}]},{name:"google.com",prehideSelectors:[".HTjtHe#xe7COe"],detectCmp:[{exists:".HTjtHe#xe7COe"},{exists:'.HTjtHe#xe7COe a[href^="https://policies.google.com/technologies/cookies"]'}],detectPopup:[{visible:".HTjtHe#xe7COe button#W0wltc"}],optIn:[{waitForThenClick:".HTjtHe#xe7COe button#L2AGLb"}],optOut:[{waitForThenClick:".HTjtHe#xe7COe button#W0wltc"}],test:[{eval:"EVAL_GOOGLE_0"}]},{name:"gov.uk",detectCmp:[{exists:"#global-cookie-message"}],detectPopup:[{exists:"#global-cookie-message"}],optIn:[{click:"button[data-accept-cookies=true]"}],optOut:[{click:"button[data-reject-cookies=true],#reject-cookies"},{click:"button[data-hide-cookie-banner=true],#hide-cookie-decision"}]},{name:"hashicorp",vendorUrl:"https://hashicorp.com/",runContext:{urlPattern:"^https://[^.]*\\.hashicorp\\.com/"},prehideSelectors:["[data-testid=consent-banner]"],detectCmp:[{exists:"[data-testid=consent-banner]"}],detectPopup:[{visible:"[data-testid=consent-banner]"}],optIn:[{waitForThenClick:"[data-testid=accept]"}],optOut:[{waitForThenClick:"[data-testid=manage-preferences]"},{waitForThenClick:"[data-testid=consent-mgr-dialog] [data-ga-button=save-preferences]"}]},{name:"healthline-media",prehideSelectors:["#modal-host > div.no-hash > div.window-wrapper"],detectCmp:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],detectPopup:[{exists:"#modal-host > div.no-hash > div.window-wrapper, div[data-testid=qualtrics-container]"}],optIn:[{click:"#modal-host > div.no-hash > div.window-wrapper > div:last-child button"}],optOut:[{if:{exists:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'},then:[{click:'#modal-host > div.no-hash > div.window-wrapper > div:last-child a[href="/privacy-settings"]'}],else:[{waitForVisible:"div#__next"},{click:"#__next div:nth-child(1) > button:first-child"}]}]},{name:"hema",prehideSelectors:[".cookie-modal"],detectCmp:[{visible:".cookie-modal .cookie-accept-btn"}],detectPopup:[{visible:".cookie-modal .cookie-accept-btn"}],optIn:[{waitForThenClick:".cookie-modal .cookie-accept-btn"}],optOut:[{waitForThenClick:".cookie-modal .js-cookie-reject-btn"}],test:[{eval:"EVAL_HEMA_TEST_0"}]},{name:"hetzner.com",runContext:{urlPattern:"^https://www\\.hetzner\\.com/"},prehideSelectors:["#CookieConsent"],detectCmp:[{exists:"#CookieConsent"}],detectPopup:[{visible:"#CookieConsent"}],optIn:[{click:"#CookieConsentGiven"}],optOut:[{click:"#CookieConsentDeclined"}]},{name:"hl.co.uk",prehideSelectors:[".cookieModalContent","#cookie-banner-overlay"],detectCmp:[{exists:"#cookie-banner-overlay"}],detectPopup:[{exists:"#cookie-banner-overlay"}],optIn:[{click:"#acceptCookieButton"}],optOut:[{click:"#manageCookie"},{hide:".cookieSettingsModal"},{waitFor:"#AOCookieToggle"},{click:"#AOCookieToggle[aria-pressed=true]",optional:!0},{waitFor:"#TPCookieToggle"},{click:"#TPCookieToggle[aria-pressed=true]",optional:!0},{click:"#updateCookieButton"}]},{name:"hu-manity",vendorUrl:"https://hu-manity.co/",prehideSelectors:["#hu.hu-wrapper"],detectCmp:[{exists:"#hu.hu-visible"}],detectPopup:[{visible:"#hu.hu-visible"}],optIn:[{waitForThenClick:"[data-hu-action=cookies-notice-consent-choices-3]"},{waitForThenClick:"#hu-cookies-save"}],optOut:[{waitForThenClick:"#hu-cookies-save"}]},{name:"hubspot",detectCmp:[{exists:"#hs-eu-cookie-confirmation"}],detectPopup:[{visible:"#hs-eu-cookie-confirmation"}],optIn:[{click:"#hs-eu-confirmation-button"}],optOut:[{click:"#hs-eu-decline-button"}]},{name:"indeed.com",cosmetic:!0,prehideSelectors:["#CookiePrivacyNotice"],detectCmp:[{exists:"#CookiePrivacyNotice"}],detectPopup:[{visible:"#CookiePrivacyNotice"}],optIn:[{click:"#CookiePrivacyNotice button[data-gnav-element-name=CookiePrivacyNoticeOk]"}],optOut:[{hide:"#CookiePrivacyNotice"}]},{name:"ing.de",runContext:{urlPattern:"^https://www\\.ing\\.de/"},cosmetic:!0,prehideSelectors:['div[slot="backdrop"]'],detectCmp:[{exists:'[data-tag-name="ing-cc-dialog-frame"]'}],detectPopup:[{visible:'[data-tag-name="ing-cc-dialog-frame"]'}],optIn:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="accept"]']}],optOut:[{click:['[data-tag-name="ing-cc-dialog-level0"]','[data-tag-name="ing-cc-button"][class*="more"]']}]},{name:"instagram",vendorUrl:"https://instagram.com",runContext:{urlPattern:"^https://www\\.instagram\\.com/"},prehideSelectors:[".x78zum5.xdt5ytf.xg6iff7.x1n2onr6"],detectCmp:[{exists:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],detectPopup:[{visible:".x1qjc9v5.x9f619.x78zum5.xdt5ytf.x1iyjqo2.xl56j7k"}],optIn:[{waitForThenClick:"._a9--._a9_0"}],optOut:[{waitForThenClick:"._a9--._a9_1"},{wait:2e3}]},{name:"ionos.de",prehideSelectors:[".privacy-consent--backdrop",".privacy-consent--modal"],detectCmp:[{exists:".privacy-consent--modal"}],detectPopup:[{visible:".privacy-consent--modal"}],optIn:[{click:"#selectAll"}],optOut:[{click:".footer-config-link"},{click:"#confirmSelection"}]},{name:"itopvpn.com",cosmetic:!0,prehideSelectors:[".pop-cookie"],detectCmp:[{exists:".pop-cookie"}],detectPopup:[{exists:".pop-cookie"}],optIn:[{click:"#_pcookie"}],optOut:[{hide:".pop-cookie"}]},{name:"iubenda",prehideSelectors:["#iubenda-cs-banner"],detectCmp:[{exists:"#iubenda-cs-banner"}],detectPopup:[{visible:".iubenda-cs-accept-btn"}],optIn:[{click:".iubenda-cs-accept-btn"}],optOut:[{click:".iubenda-cs-customize-btn"},{eval:"EVAL_IUBENDA_0"},{click:"#iubFooterBtn"}],test:[{eval:"EVAL_IUBENDA_1"}]},{name:"iWink",prehideSelectors:["body.cookies-request #cookie-bar"],detectCmp:[{exists:"body.cookies-request #cookie-bar"}],detectPopup:[{visible:"body.cookies-request #cookie-bar"}],optIn:[{waitForThenClick:"body.cookies-request #cookie-bar .allow-cookies"}],optOut:[{waitForThenClick:"body.cookies-request #cookie-bar .disallow-cookies"}],test:[{eval:"EVAL_IWINK_TEST"}]},{name:"jdsports",vendorUrl:"https://www.jdsports.co.uk/",runContext:{urlPattern:"^https://(www|m)\\.jdsports\\."},prehideSelectors:[".miniConsent,#PrivacyPolicyBanner"],detectCmp:[{exists:".miniConsent,#PrivacyPolicyBanner"}],detectPopup:[{visible:".miniConsent,#PrivacyPolicyBanner"}],optIn:[{waitForThenClick:".miniConsent .accept-all-cookies"}],optOut:[{if:{exists:"#PrivacyPolicyBanner"},then:[{hide:"#PrivacyPolicyBanner"}],else:[{waitForThenClick:"#cookie-settings"},{waitForThenClick:"#reject-all-cookies"}]}]},{name:"johnlewis.com",prehideSelectors:["div[class^=pecr-cookie-banner-]"],detectCmp:[{exists:"div[class^=pecr-cookie-banner-]"}],detectPopup:[{exists:"div[class^=pecr-cookie-banner-]"}],optOut:[{click:"button[data-test^=manage-cookies]"},{wait:"500"},{click:"label[data-test^=toggle][class*=checked]:not([class*=disabled])",all:!0,optional:!0},{click:"button[data-test=save-preferences]"}],optIn:[{click:"button[data-test=allow-all]"}]},{name:"jquery.cookieBar",vendorUrl:"https://github.com/kovarp/jquery.cookieBar",prehideSelectors:[".cookie-bar"],cosmetic:!0,detectCmp:[{exists:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons"}],detectPopup:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"any"}],optIn:[{click:".cookie-bar .cookie-bar__btn"}],optOut:[{hide:".cookie-bar"}],test:[{visible:".cookie-bar .cookie-bar__message,.cookie-bar .cookie-bar__buttons",check:"none"},{eval:"EVAL_JQUERY_COOKIEBAR_0"}]},{name:"justwatch.com",prehideSelectors:[".consent-banner"],detectCmp:[{exists:".consent-banner .consent-banner__actions"}],detectPopup:[{visible:".consent-banner .consent-banner__actions"}],optIn:[{click:".consent-banner__actions button.basic-button.primary"}],optOut:[{click:".consent-banner__actions button.basic-button.secondary"},{waitForThenClick:".consent-modal__footer button.basic-button.secondary"},{waitForThenClick:".consent-modal ion-content > div > a:nth-child(9)"},{click:"label.consent-switch input[type=checkbox]:checked",all:!0,optional:!0},{waitForVisible:".consent-modal__footer button.basic-button.primary"},{click:".consent-modal__footer button.basic-button.primary"}]},{name:"ketch",vendorUrl:"https://www.ketch.com",runContext:{frame:!1,main:!0},intermediate:!1,prehideSelectors:["#lanyard_root div[role='dialog']"],detectCmp:[{exists:"#lanyard_root div[role='dialog']"}],detectPopup:[{visible:"#lanyard_root div[role='dialog']"}],optIn:[{if:{exists:"#lanyard_root button[class='confirmButton']"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"},{click:"#lanyard_root button[class='confirmButton']"}],else:[{waitForThenClick:"#lanyard_root div[class*=buttons] > :nth-child(2)"}]}],optOut:[{if:{exists:"#lanyard_root [aria-describedby=banner-description]"},then:[{waitForThenClick:"#lanyard_root div[class*=buttons] > button[class*=secondaryButton]",comment:"can be either settings or reject button"}]},{waitFor:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]",timeout:1e3,optional:!0},{if:{exists:"#lanyard_root [aria-describedby=preference-description],#lanyard_root [aria-describedby=modal-description]"},then:[{waitForThenClick:"#lanyard_root button[class*=rejectButton]"},{click:"#lanyard_root button[class*=confirmButton],#lanyard_root div[class*=actions_] > button:nth-child(1)"}]}]},{name:"kleinanzeigen-de",runContext:{urlPattern:"^https?://(www\\.)?kleinanzeigen\\.de"},prehideSelectors:["#gdpr-banner-container"],detectCmp:[{any:[{exists:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{exists:"#ConsentManagementPage"}]}],detectPopup:[{any:[{visible:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"},{visible:"#ConsentManagementPage"}]}],optIn:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-accept]"}],else:[{click:"#ConsentManagementPage .Button-primary"}]}],optOut:[{if:{exists:"#gdpr-banner-container #gdpr-banner"},then:[{click:"#gdpr-banner-container #gdpr-banner [data-testid=gdpr-banner-cmp-button]"}],else:[{click:"#ConsentManagementPage .Button-secondary"}]}]},{name:"lightbox",prehideSelectors:[".darken-layer.open,.lightbox.lightbox--cookie-consent"],detectCmp:[{exists:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],detectPopup:[{visible:"body.cookie-consent-is-active div.lightbox--cookie-consent > div.lightbox__content > div.cookie-consent[data-jsb]"}],optOut:[{click:".cookie-consent__footer > button[type='submit']:not([data-button='selectAll'])"}],optIn:[{click:".cookie-consent__footer > button[type='submit'][data-button='selectAll']"}]},{name:"lineagrafica",vendorUrl:"https://addons.prestashop.com/en/legal/8734-eu-cookie-law-gdpr-banner-blocker.html",cosmetic:!0,prehideSelectors:["#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"],detectCmp:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],detectPopup:[{exists:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}],optIn:[{waitForThenClick:"#lgcookieslaw_accept"}],optOut:[{hide:"#lgcookieslaw_banner,#lgcookieslaw_modal,.lgcookieslaw-overlay"}]},{name:"linkedin.com",prehideSelectors:[".artdeco-global-alert[type=COOKIE_CONSENT]"],detectCmp:[{exists:".artdeco-global-alert[type=COOKIE_CONSENT]"}],detectPopup:[{visible:".artdeco-global-alert[type=COOKIE_CONSENT]"}],optIn:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=ACCEPT]"}],optOut:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"},{wait:500},{waitForThenClick:".artdeco-global-alert[type=COOKIE_CONSENT] button[action-type=DENY]"}],test:[{waitForVisible:".artdeco-global-alert[type=COOKIE_CONSENT]",check:"none"}]},{name:"livejasmin",vendorUrl:"https://www.livejasmin.com/",runContext:{urlPattern:"^https://(m|www)\\.livejasmin\\.com/"},prehideSelectors:["#consent_modal"],detectCmp:[{exists:"#consent_modal"}],detectPopup:[{visible:"#consent_modal"}],optIn:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:first-of-type"}],optOut:[{waitForThenClick:"#consent_modal button[data-testid=ButtonStyledButton]:nth-of-type(2)"},{waitForVisible:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent]"},{click:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] input[data-testid=PrivacyPreferenceCenterWithConsentCookieSwitch]:checked",optional:!0,all:!0},{waitForThenClick:"[data-testid=PrivacyPreferenceCenterWithConsentCookieContent] button[data-testid=ButtonStyledButton]:last-child"}]},{name:"macpaw.com",cosmetic:!0,prehideSelectors:['div[data-banner="cookies"]'],detectCmp:[{exists:'div[data-banner="cookies"]'}],detectPopup:[{exists:'div[data-banner="cookies"]'}],optIn:[{click:'button[data-banner-close="cookies"]'}],optOut:[{hide:'div[data-banner="cookies"]'}]},{name:"marksandspencer.com",cosmetic:!0,detectCmp:[{exists:".navigation-cookiebbanner"}],detectPopup:[{visible:".navigation-cookiebbanner"}],optOut:[{hide:".navigation-cookiebbanner"}],optIn:[{click:".navigation-cookiebbanner__submit"}]},{name:"mediamarkt.de",prehideSelectors:["div[aria-labelledby=pwa-consent-layer-title]","div[class^=StyledConsentLayerWrapper-]"],detectCmp:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],detectPopup:[{exists:"div[aria-labelledby^=pwa-consent-layer-title]"}],optOut:[{click:"button[data-test^=pwa-consent-layer-deny-all]"}],optIn:[{click:"button[data-test^=pwa-consent-layer-accept-all"}]},{name:"Mediavine",prehideSelectors:['[data-name="mediavine-gdpr-cmp"]'],detectCmp:[{exists:'[data-name="mediavine-gdpr-cmp"]'}],detectPopup:[{wait:500},{visible:'[data-name="mediavine-gdpr-cmp"]'}],optIn:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [format="primary"]'}],optOut:[{waitForThenClick:'[data-name="mediavine-gdpr-cmp"] [data-view="manageSettings"]'},{waitFor:'[data-name="mediavine-gdpr-cmp"] input[type=checkbox]'},{eval:"EVAL_MEDIAVINE_0",optional:!0},{click:'[data-name="mediavine-gdpr-cmp"] [format="secondary"]'}]},{name:"microsoft.com",prehideSelectors:["#wcpConsentBannerCtrl"],detectCmp:[{exists:"#wcpConsentBannerCtrl"}],detectPopup:[{exists:"#wcpConsentBannerCtrl"}],optOut:[{eval:"EVAL_MICROSOFT_0"}],optIn:[{eval:"EVAL_MICROSOFT_1"}],test:[{eval:"EVAL_MICROSOFT_2"}]},{name:"midway-usa",runContext:{urlPattern:"^https://www\\.midwayusa\\.com/"},cosmetic:!0,prehideSelectors:["#cookie-container"],detectCmp:[{exists:['div[aria-label="Cookie Policy Banner"]']}],detectPopup:[{visible:"#cookie-container"}],optIn:[{click:"button#cookie-btn"}],optOut:[{hide:'div[aria-label="Cookie Policy Banner"]'}]},{name:"moneysavingexpert.com",detectCmp:[{exists:"dialog[data-testid=accept-our-cookies-dialog]"}],detectPopup:[{visible:"dialog[data-testid=accept-our-cookies-dialog]"}],optIn:[{click:"#banner-accept"}],optOut:[{click:"#banner-manage"},{click:"#pc-confirm"}]},{name:"monzo.com",prehideSelectors:[".cookie-alert, cookie-alert__content"],detectCmp:[{exists:'div.cookie-alert[role="dialog"]'},{exists:'a[href*="monzo"]'}],detectPopup:[{visible:".cookie-alert__content"}],optIn:[{click:".js-accept-cookie-policy"}],optOut:[{click:".js-decline-cookie-policy"}]},{name:"Moove",prehideSelectors:["#moove_gdpr_cookie_info_bar"],detectCmp:[{exists:"#moove_gdpr_cookie_info_bar"}],detectPopup:[{visible:"#moove_gdpr_cookie_info_bar:not(.moove-gdpr-info-bar-hidden)"}],optIn:[{waitForThenClick:".moove-gdpr-infobar-allow-all"}],optOut:[{if:{exists:"#moove_gdpr_cookie_info_bar .change-settings-button"},then:[{click:"#moove_gdpr_cookie_info_bar .change-settings-button"},{waitForVisible:"#moove_gdpr_cookie_modal"},{eval:"EVAL_MOOVE_0"},{click:".moove-gdpr-modal-save-settings"}],else:[{hide:"#moove_gdpr_cookie_info_bar"}]}],test:[{visible:"#moove_gdpr_cookie_info_bar",check:"none"}]},{name:"national-lottery.co.uk",detectCmp:[{exists:".cuk_cookie_consent"}],detectPopup:[{visible:".cuk_cookie_consent",check:"any"}],optOut:[{click:".cuk_cookie_consent_manage_pref"},{click:".cuk_cookie_consent_save_pref"},{click:".cuk_cookie_consent_close"}],optIn:[{click:".cuk_cookie_consent_accept_all"}]},{name:"nba.com",runContext:{urlPattern:"^https://(www\\.)?nba.com/"},cosmetic:!0,prehideSelectors:["#onetrust-banner-sdk"],detectCmp:[{exists:"#onetrust-banner-sdk"}],detectPopup:[{visible:"#onetrust-banner-sdk"}],optIn:[{click:"#onetrust-accept-btn-handler"}],optOut:[{hide:"#onetrust-banner-sdk"}]},{name:"netflix.de",detectCmp:[{exists:"#cookie-disclosure"}],detectPopup:[{visible:".cookie-disclosure-message",check:"any"}],optIn:[{click:".btn-accept"}],optOut:[{hide:"#cookie-disclosure"},{click:".btn-reject"}]},{name:"nhs.uk",prehideSelectors:["#nhsuk-cookie-banner"],detectCmp:[{exists:"#nhsuk-cookie-banner"}],detectPopup:[{exists:"#nhsuk-cookie-banner"}],optOut:[{click:"#nhsuk-cookie-banner__link_accept"}],optIn:[{click:"#nhsuk-cookie-banner__link_accept_analytics"}]},{name:"notice-cookie",prehideSelectors:[".button--notice"],cosmetic:!0,detectCmp:[{exists:".notice--cookie"}],detectPopup:[{visible:".notice--cookie"}],optIn:[{click:".button--notice"}],optOut:[{hide:".notice--cookie"}]},{name:"nrk.no",cosmetic:!0,prehideSelectors:[".nrk-masthead__info-banner--cookie"],detectCmp:[{exists:".nrk-masthead__info-banner--cookie"}],detectPopup:[{exists:".nrk-masthead__info-banner--cookie"}],optIn:[{click:"div.nrk-masthead__info-banner--cookie button > span:has(+ svg.nrk-close)"}],optOut:[{hide:".nrk-masthead__info-banner--cookie"}]},{name:"obi.de",prehideSelectors:[".disc-cp--active"],detectCmp:[{exists:".disc-cp-modal__modal"}],detectPopup:[{visible:".disc-cp-modal__modal"}],optIn:[{click:".js-disc-cp-accept-all"}],optOut:[{click:".js-disc-cp-deny-all"}]},{name:"om",vendorUrl:"https://olli-machts.de/en/extension/cookie-manager",prehideSelectors:[".tx-om-cookie-consent"],detectCmp:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],detectPopup:[{exists:".tx-om-cookie-consent .active[data-omcookie-panel]"}],optIn:[{waitForThenClick:"[data-omcookie-panel-save=all]"}],optOut:[{if:{exists:"[data-omcookie-panel-save=min]"},then:[{waitForThenClick:"[data-omcookie-panel-save=min]"}],else:[{click:"input[data-omcookie-panel-grp]:checked:not(:disabled)",all:!0,optional:!0},{waitForThenClick:"[data-omcookie-panel-save=save]"}]}]},{name:"onlyFans.com",runContext:{urlPattern:"^https://onlyfans\\.com/"},prehideSelectors:["div.b-cookies-informer"],detectCmp:[{exists:"div.b-cookies-informer"}],detectPopup:[{exists:"div.b-cookies-informer"}],optIn:[{click:"div.b-cookies-informer__nav > button:nth-child(2)"}],optOut:[{click:"div.b-cookies-informer__nav > button:nth-child(1)"},{if:{exists:"div.b-cookies-informer__switchers"},then:[{click:"div.b-cookies-informer__switchers input:not([disabled])",all:!0},{click:"div.b-cookies-informer__nav > button"}]}]},{name:"openli",vendorUrl:"https://openli.com",prehideSelectors:[".legalmonster-cleanslate"],detectCmp:[{exists:".legalmonster-cleanslate"}],detectPopup:[{visible:".legalmonster-cleanslate #lm-cookie-wall-container",check:"any"}],optIn:[{waitForThenClick:"#lm-accept-all"}],optOut:[{waitForThenClick:"#lm-accept-necessary"}]},{name:"opera.com",vendorUrl:"https://unknown",cosmetic:!1,runContext:{main:!0,frame:!1},intermediate:!1,prehideSelectors:[],detectCmp:[{exists:"#cookie-consent .manage-cookies__btn"}],detectPopup:[{visible:"#cookie-consent .cookie-basic-consent__btn"}],optIn:[{waitForThenClick:"#cookie-consent .cookie-basic-consent__btn"}],optOut:[{waitForThenClick:"#cookie-consent .manage-cookies__btn"},{waitForThenClick:"#cookie-consent .active.marketing_option_switch.cookie-consent__switch",all:!0},{waitForThenClick:"#cookie-consent .cookie-selection__btn"}],test:[{eval:"EVAL_OPERA_0"}]},{name:"osano",prehideSelectors:[".osano-cm-window,.osano-cm-dialog"],detectCmp:[{exists:".osano-cm-window"}],detectPopup:[{visible:".osano-cm-dialog"}],optIn:[{click:".osano-cm-accept-all",optional:!0}],optOut:[{waitForThenClick:".osano-cm-denyAll"}]},{name:"otto.de",prehideSelectors:[".cookieBanner--visibility"],detectCmp:[{exists:".cookieBanner--visibility"}],detectPopup:[{visible:".cookieBanner__wrapper"}],optIn:[{click:".js_cookieBannerPermissionButton"}],optOut:[{click:".js_cookieBannerProhibitionButton"}]},{name:"ourworldindata",vendorUrl:"https://ourworldindata.org/",runContext:{urlPattern:"^https://ourworldindata\\.org/"},prehideSelectors:[".cookie-manager"],detectCmp:[{exists:".cookie-manager"}],detectPopup:[{visible:".cookie-manager .cookie-notice.open"}],optIn:[{waitForThenClick:".cookie-notice [data-test=accept]"}],optOut:[{waitForThenClick:".cookie-notice [data-test=reject]"}]},{name:"pabcogypsum",vendorUrl:"https://unknown",prehideSelectors:[".js-cookie-notice:has(#cookie_settings-form)"],detectCmp:[{exists:".js-cookie-notice #cookie_settings-form"}],detectPopup:[{visible:".js-cookie-notice #cookie_settings-form"}],optIn:[{waitForThenClick:".js-cookie-notice button[value=allow]"}],optOut:[{waitForThenClick:".js-cookie-notice button[value=disable]"}]},{name:"paypal-us",prehideSelectors:["#ccpaCookieContent_wrapper, article.ppvx_modal--overpanel"],detectCmp:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],detectPopup:[{exists:"#ccpaCookieBanner, .privacy-sheet-content"}],optIn:[{click:"#acceptAllButton"}],optOut:[{if:{exists:"a#manageCookiesLink"},then:[{click:"a#manageCookiesLink"}],else:[{waitForVisible:".privacy-sheet-content #formContent"},{click:"#formContent .cookiepref-11m2iee-checkbox_base input:checked",all:!0,optional:!0},{click:".confirmCookie #submitCookiesBtn"}]}]},{name:"paypal.com",prehideSelectors:["#gdprCookieBanner"],detectCmp:[{exists:"#gdprCookieBanner"}],detectPopup:[{visible:"#gdprCookieContent_wrapper"}],optIn:[{click:"#acceptAllButton"}],optOut:[{wait:200},{click:".gdprCookieBanner_decline-button"}],test:[{wait:500},{eval:"EVAL_PAYPAL_0"}]},{name:"pinetools.com",cosmetic:!0,prehideSelectors:["#aviso_cookies"],detectCmp:[{exists:"#aviso_cookies"}],detectPopup:[{exists:".lang_en #aviso_cookies"}],optIn:[{click:"#aviso_cookies .a_boton_cerrar"}],optOut:[{hide:"#aviso_cookies"}]},{name:"pmc",cosmetic:!0,prehideSelectors:["#pmc-pp-tou--notice"],detectCmp:[{exists:"#pmc-pp-tou--notice"}],detectPopup:[{visible:"#pmc-pp-tou--notice"}],optIn:[{click:"span.pmc-pp-tou--notice-close-btn"}],optOut:[{hide:"#pmc-pp-tou--notice"}]},{name:"pornhub.com",runContext:{urlPattern:"^https://(www\\.)?pornhub\\.com/"},cosmetic:!0,prehideSelectors:[".cookiesBanner"],detectCmp:[{exists:".cookiesBanner"}],detectPopup:[{visible:".cookiesBanner"}],optIn:[{click:".cookiesBanner .okButton"}],optOut:[{hide:".cookiesBanner"}]},{name:"pornpics.com",cosmetic:!0,prehideSelectors:["#cookie-contract"],detectCmp:[{exists:"#cookie-contract"}],detectPopup:[{visible:"#cookie-contract"}],optIn:[{click:"#cookie-contract .icon-cross"}],optOut:[{hide:"#cookie-contract"}]},{name:"PrimeBox CookieBar",prehideSelectors:["#cookie-bar"],detectCmp:[{exists:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy"}],detectPopup:[{visible:"#cookie-bar .cb-enable,#cookie-bar .cb-disable,#cookie-bar .cb-policy",check:"any"}],optIn:[{waitForThenClick:"#cookie-bar .cb-enable"}],optOut:[{click:"#cookie-bar .cb-disable",optional:!0},{hide:"#cookie-bar"}],test:[{eval:"EVAL_PRIMEBOX_0"}]},{name:"privacymanager.io",prehideSelectors:["#gdpr-consent-tool-wrapper",'iframe[src^="https://cmp-consent-tool.privacymanager.io"]'],runContext:{urlPattern:"^https://cmp-consent-tool\\.privacymanager\\.io/",main:!1,frame:!0},detectCmp:[{exists:"button#save"}],detectPopup:[{visible:"button#save"}],optIn:[{click:"button#save"}],optOut:[{if:{exists:"#denyAll"},then:[{click:"#denyAll"},{waitForThenClick:".okButton"}],else:[{waitForThenClick:"#manageSettings"},{waitFor:".purposes-overview-list"},{waitFor:"button#saveAndExit"},{click:"span[role=checkbox][aria-checked=true]",all:!0,optional:!0},{click:"button#saveAndExit"}]}]},{name:"productz.com",vendorUrl:"https://productz.com/",runContext:{urlPattern:"^https://productz\\.com/"},prehideSelectors:[],detectCmp:[{exists:".c-modal.is-active"}],detectPopup:[{visible:".c-modal.is-active"}],optIn:[{waitForThenClick:".c-modal.is-active .is-accept"}],optOut:[{waitForThenClick:".c-modal.is-active .is-dismiss"}]},{name:"pubtech",prehideSelectors:["#pubtech-cmp"],detectCmp:[{exists:"#pubtech-cmp"}],detectPopup:[{visible:"#pubtech-cmp #pt-actions"}],optIn:[{if:{exists:"#pt-accept-all"},then:[{click:"#pubtech-cmp #pt-actions #pt-accept-all"}],else:[{click:"#pubtech-cmp #pt-actions button:nth-of-type(2)"}]}],optOut:[{click:"#pubtech-cmp #pt-close"}],test:[{eval:"EVAL_PUBTECH_0"}]},{name:"quantcast",prehideSelectors:["#qc-cmp2-main,#qc-cmp2-container"],detectCmp:[{exists:"#qc-cmp2-container"}],detectPopup:[{visible:"#qc-cmp2-ui"}],optOut:[{click:'.qc-cmp2-summary-buttons > button[mode="secondary"]'},{waitFor:"#qc-cmp2-ui"},{click:'.qc-cmp2-toggle-switch > button[aria-checked="true"]',all:!0,optional:!0},{click:'.qc-cmp2-main button[aria-label="REJECT ALL"]',optional:!0},{waitForThenClick:'.qc-cmp2-main button[aria-label="SAVE & EXIT"],.qc-cmp2-buttons-desktop > button[mode="primary"]',timeout:5e3}],optIn:[{click:'.qc-cmp2-summary-buttons > button[mode="primary"]'}]},{name:"reddit.com",runContext:{urlPattern:"^https://www\\.reddit\\.com/"},prehideSelectors:["[bundlename=reddit_cookie_banner]"],detectCmp:[{exists:"reddit-cookie-banner"}],detectPopup:[{visible:"reddit-cookie-banner"}],optIn:[{waitForThenClick:["reddit-cookie-banner","#accept-all-cookies-button > button"]}],optOut:[{waitForThenClick:["reddit-cookie-banner","#reject-nonessential-cookies-button > button"]}],test:[{eval:"EVAL_REDDIT_0"}]},{name:"rog-forum.asus.com",runContext:{urlPattern:"^https://rog-forum\\.asus\\.com/"},prehideSelectors:["#cookie-policy-info"],detectCmp:[{exists:"#cookie-policy-info"}],detectPopup:[{visible:"#cookie-policy-info"}],optIn:[{click:'div.cookie-btn-box > div[aria-label="Accept"]'}],optOut:[{click:'div.cookie-btn-box > div[aria-label="Reject"]'},{waitForThenClick:'.cookie-policy-lightbox-bottom > div[aria-label="Save Settings"]'}]},{name:"roofingmegastore.co.uk",runContext:{urlPattern:"^https://(www\\.)?roofingmegastore\\.co\\.uk"},prehideSelectors:["#m-cookienotice"],detectCmp:[{exists:"#m-cookienotice"}],detectPopup:[{visible:"#m-cookienotice"}],optIn:[{click:"#accept-cookies"}],optOut:[{click:"#manage-cookies"},{waitForThenClick:"#accept-selected"}]},{name:"samsung.com",runContext:{urlPattern:"^https://www\\.samsung\\.com/"},cosmetic:!0,prehideSelectors:["div.cookie-bar"],detectCmp:[{exists:"div.cookie-bar"}],detectPopup:[{visible:"div.cookie-bar"}],optIn:[{click:"div.cookie-bar__manage > a"}],optOut:[{hide:"div.cookie-bar"}]},{name:"setapp.com",vendorUrl:"https://setapp.com/",cosmetic:!0,runContext:{urlPattern:"^https://setapp\\.com/"},prehideSelectors:[],detectCmp:[{exists:".cookie-banner.js-cookie-banner"}],detectPopup:[{visible:".cookie-banner.js-cookie-banner"}],optIn:[{waitForThenClick:".cookie-banner.js-cookie-banner button"}],optOut:[{hide:".cookie-banner.js-cookie-banner"}]},{name:"sibbo",prehideSelectors:["sibbo-cmp-layout"],detectCmp:[{exists:"sibbo-cmp-layout"}],detectPopup:[{visible:"sibbo-cmp-layout"}],optIn:[{click:"sibbo-cmp-layout [data-accept-all]"}],optOut:[{click:'.sibbo-panel__aside__buttons a[data-nav="purposes"]'},{click:'.sibbo-panel__main__header__actions a[data-focusable="reject-all"]'},{if:{exists:"[data-view=purposes] .sibbo-panel__main__footer__actions [data-save-and-exit]"},then:[],else:[{waitFor:'.sibbo-panel__main__footer__actions a[data-focusable="next"]:not(.sibbo-cmp-button--disabled)'},{click:'.sibbo-panel__main__footer__actions a[data-focusable="next"]'},{click:'.sibbo-panel__main div[data-view="purposesLegInt"] a[data-focusable="reject-all"]'}]},{waitFor:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"},{click:".sibbo-panel__main__footer__actions [data-save-and-exit]:not(.sibbo-cmp-button--disabled)"}],test:[{eval:"EVAL_SIBBO_0"}]},{name:"similarweb.com",cosmetic:!0,prehideSelectors:[".app-cookies-notification"],detectCmp:[{exists:".app-cookies-notification"}],detectPopup:[{exists:".app-layout .app-cookies-notification"}],optIn:[{click:"button.app-cookies-notification__dismiss"}],optOut:[{hide:".app-layout .app-cookies-notification"}]},{name:"Sirdata",cosmetic:!1,prehideSelectors:["#sd-cmp"],detectCmp:[{exists:"#sd-cmp"}],detectPopup:[{visible:"#sd-cmp"}],optIn:[{waitForThenClick:"#sd-cmp .sd-cmp-3cRQ2"}],optOut:[{waitForThenClick:["#sd-cmp","xpath///span[contains(., 'Do not accept') or contains(., 'Acceptera inte') or contains(., 'No aceptar') or contains(., 'Ikke acceptere') or contains(., 'Nicht akzeptieren') or contains(., 'Не приемам') or contains(., 'Να μην γίνει αποδοχή') or contains(., 'Niet accepteren') or contains(., 'Nepřijímat') or contains(., 'Nie akceptuj') or contains(., 'Nu acceptați') or contains(., 'Não aceitar') or contains(., 'Continuer sans accepter') or contains(., 'Non accettare') or contains(., 'Nem fogad el')]"]}]},{name:"snigel",detectCmp:[{exists:".snigel-cmp-framework"}],detectPopup:[{visible:".snigel-cmp-framework"}],optOut:[{click:"#sn-b-custom"},{click:"#sn-b-save"}],test:[{eval:"EVAL_SNIGEL_0"}],optIn:[{click:".snigel-cmp-framework #accept-choices"}]},{name:"steampowered.com",detectCmp:[{exists:".cookiepreferences_popup"},{visible:".cookiepreferences_popup"}],detectPopup:[{visible:".cookiepreferences_popup"}],optOut:[{click:"#rejectAllButton"}],optIn:[{click:"#acceptAllButton"}],test:[{wait:1e3},{eval:"EVAL_STEAMPOWERED_0"}]},{name:"strato.de",prehideSelectors:[".consent__wrapper"],runContext:{urlPattern:"^https://www\\.strato\\.de/"},detectCmp:[{exists:".consent"}],detectPopup:[{visible:".consent"}],optIn:[{click:"button.consentAgree"}],optOut:[{click:"button.consentSettings"},{waitForThenClick:"button#consentSubmit"}]},{name:"svt.se",vendorUrl:"https://www.svt.se/",runContext:{urlPattern:"^https://www\\.svt\\.se/"},prehideSelectors:["[class*=CookieConsent__root___]"],detectCmp:[{exists:"[class*=CookieConsent__root___]"}],detectPopup:[{visible:"[class*=CookieConsent__modal___]"}],optIn:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=primary]"}],optOut:[{waitForThenClick:"[class*=CookieConsent__modal___] > div > button[class*=secondary]:nth-child(2)"}],test:[{eval:"EVAL_SVT_TEST"}]},{name:"takealot.com",cosmetic:!0,prehideSelectors:['div[class^="cookies-banner-module_"]'],detectCmp:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],detectPopup:[{exists:'div[class^="cookies-banner-module_cookie-banner_"]'}],optIn:[{click:'button[class*="cookies-banner-module_dismiss-button_"]'}],optOut:[{hide:'div[class^="cookies-banner-module_"]'},{if:{exists:'div[class^="cookies-banner-module_small-cookie-banner_"]'},then:[{eval:"EVAL_TAKEALOT_0"}],else:[]}]},{name:"tarteaucitron.js",prehideSelectors:["#tarteaucitronRoot"],detectCmp:[{exists:"#tarteaucitronRoot"}],detectPopup:[{visible:"#tarteaucitronRoot #tarteaucitronAlertSmall,#tarteaucitronRoot #tarteaucitronAlertBig",check:"any"}],optIn:[{eval:"EVAL_TARTEAUCITRON_1"}],optOut:[{eval:"EVAL_TARTEAUCITRON_0"}],test:[{eval:"EVAL_TARTEAUCITRON_2",comment:"sometimes there are required categories, so we check that at least something is false"}]},{name:"taunton",vendorUrl:"https://www.taunton.com/",prehideSelectors:["#taunton-user-consent__overlay"],detectCmp:[{exists:"#taunton-user-consent__overlay"}],detectPopup:[{exists:"#taunton-user-consent__overlay:not([aria-hidden=true])"}],optIn:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:not(:checked)"},{click:"#taunton-user-consent__toolbar button[type=submit]"}],optOut:[{click:"#taunton-user-consent__toolbar input[type=checkbox]:checked",optional:!0,all:!0},{click:"#taunton-user-consent__toolbar button[type=submit]"}],test:[{eval:"EVAL_TAUNTON_TEST"}]},{name:"Tealium",prehideSelectors:["#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal,#consent-layer"],detectCmp:[{exists:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *"},{eval:"EVAL_TEALIUM_0"}],detectPopup:[{visible:"#__tealiumGDPRecModal *,#__tealiumGDPRcpPrefs *,#__tealiumImplicitmodal *",check:"any"}],optOut:[{eval:"EVAL_TEALIUM_1"},{eval:"EVAL_TEALIUM_DONOTSELL"},{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#__tealiumImplicitmodal"},{waitForThenClick:"#cm-acceptNone,.js-accept-essential-cookies",timeout:1e3,optional:!0}],optIn:[{hide:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs"},{eval:"EVAL_TEALIUM_2"}],test:[{eval:"EVAL_TEALIUM_3"},{eval:"EVAL_TEALIUM_DONOTSELL_CHECK"},{visible:"#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs",check:"none"}]},{name:"temu",vendorUrl:"https://temu.com",runContext:{urlPattern:"^https://[^/]*temu\\.com/"},prehideSelectors:["._2d-8vq-W,._1UdBUwni"],detectCmp:[{exists:"._3YCsmIaS"}],detectPopup:[{visible:"._3YCsmIaS"}],optIn:[{waitForThenClick:"._3fKiu5wx._3zN5SumS._3tAK973O.IYOfhWEs.VGNGF1pA"}],optOut:[{waitForThenClick:"._3fKiu5wx._1_XToJBF._3tAK973O.IYOfhWEs.VGNGF1pA"}]},{name:"Termly",prehideSelectors:["#termly-code-snippet-support"],detectCmp:[{exists:"#termly-code-snippet-support"}],detectPopup:[{visible:"#termly-code-snippet-support div"}],optIn:[{waitForThenClick:'[data-tid="banner-accept"]'}],optOut:[{if:{exists:'[data-tid="banner-decline"]'},then:[{click:'[data-tid="banner-decline"]'}],else:[{click:".t-preference-button"},{wait:500},{if:{exists:".t-declineAllButton"},then:[{click:".t-declineAllButton"}],else:[{waitForThenClick:".t-preference-modal input[type=checkbox][checked]:not([disabled])",all:!0},{waitForThenClick:".t-saveButton"}]}]}]},{name:"termsfeed",vendorUrl:"https://termsfeed.com",comment:"v4.x.x",prehideSelectors:[".termsfeed-com---nb"],detectCmp:[{exists:".termsfeed-com---nb"}],detectPopup:[{visible:".termsfeed-com---nb"}],optIn:[{waitForThenClick:".cc-nb-okagree"}],optOut:[{waitForThenClick:".cc-nb-reject"}]},{name:"termsfeed3",vendorUrl:"https://termsfeed.com",comment:"v3.x.x",cosmetic:!0,prehideSelectors:[".cc_dialog.cc_css_reboot"],detectCmp:[{exists:".cc_dialog.cc_css_reboot"}],detectPopup:[{visible:".cc_dialog.cc_css_reboot"}],optIn:[{waitForThenClick:".cc_dialog.cc_css_reboot .cc_b_ok"}],optOut:[{hide:".cc_dialog.cc_css_reboot"}]},{name:"Test page cosmetic CMP",cosmetic:!0,prehideSelectors:["#privacy-test-page-cmp-test-prehide"],detectCmp:[{exists:"#privacy-test-page-cmp-test-banner"}],detectPopup:[{visible:"#privacy-test-page-cmp-test-banner"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{hide:"#privacy-test-page-cmp-test-banner"}],test:[{wait:500},{eval:"EVAL_TESTCMP_COSMETIC_0"}]},{name:"Test page CMP",prehideSelectors:["#reject-all"],detectCmp:[{exists:"#privacy-test-page-cmp-test"}],detectPopup:[{visible:"#privacy-test-page-cmp-test"}],optIn:[{waitFor:"#accept-all"},{click:"#accept-all"}],optOut:[{waitFor:"#reject-all"},{click:"#reject-all"}],test:[{eval:"EVAL_TESTCMP_0"}]},{name:"thalia.de",prehideSelectors:[".consent-banner-box"],detectCmp:[{exists:"consent-banner[component=consent-banner]"}],detectPopup:[{visible:".consent-banner-box"}],optIn:[{click:".button-zustimmen"}],optOut:[{click:"button[data-consent=disagree]"}]},{name:"thefreedictionary.com",prehideSelectors:["#cmpBanner"],detectCmp:[{exists:"#cmpBanner"}],detectPopup:[{visible:"#cmpBanner"}],optIn:[{eval:"EVAL_THEFREEDICTIONARY_1"}],optOut:[{eval:"EVAL_THEFREEDICTIONARY_0"}]},{name:"theverge",runContext:{frame:!1,main:!0,urlPattern:"^https://(www)?\\.theverge\\.com"},intermediate:!1,prehideSelectors:[".duet--cta--cookie-banner"],detectCmp:[{exists:".duet--cta--cookie-banner"}],detectPopup:[{visible:".duet--cta--cookie-banner"}],optIn:[{click:".duet--cta--cookie-banner button.tracking-12",all:!1}],optOut:[{click:".duet--cta--cookie-banner button.tracking-12 > span"}],test:[{eval:"EVAL_THEVERGE_0"}]},{name:"tidbits-com",cosmetic:!0,prehideSelectors:["#eu_cookie_law_widget-2"],detectCmp:[{exists:"#eu_cookie_law_widget-2"}],detectPopup:[{visible:"#eu_cookie_law_widget-2"}],optIn:[{click:"#eu-cookie-law form > input.accept"}],optOut:[{hide:"#eu_cookie_law_widget-2"}]},{name:"tractor-supply",runContext:{urlPattern:"^https://www\\.tractorsupply\\.com/"},cosmetic:!0,prehideSelectors:[".tsc-cookie-banner"],detectCmp:[{exists:".tsc-cookie-banner"}],detectPopup:[{visible:".tsc-cookie-banner"}],optIn:[{click:"#cookie-banner-cancel"}],optOut:[{hide:".tsc-cookie-banner"}]},{name:"trader-joes-com",cosmetic:!0,prehideSelectors:['div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'],detectCmp:[{exists:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],detectPopup:[{visible:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}],optIn:[{click:'div[class^="CookiesAlert_cookiesAlert__container__"] button'}],optOut:[{hide:'div.aem-page > div[class^="CookiesAlert_cookiesAlert__"]'}]},{name:"transcend",vendorUrl:"https://unknown",cosmetic:!0,prehideSelectors:["#transcend-consent-manager"],detectCmp:[{exists:"#transcend-consent-manager"}],detectPopup:[{visible:"#transcend-consent-manager"}],optIn:[{waitForThenClick:["#transcend-consent-manager","#consentManagerMainDialog .inner-container button"]}],optOut:[{hide:"#transcend-consent-manager"}]},{name:"transip-nl",runContext:{urlPattern:"^https://www\\.transip\\.nl/"},prehideSelectors:["#consent-modal"],detectCmp:[{any:[{exists:"#consent-modal"},{exists:"#privacy-settings-content"}]}],detectPopup:[{any:[{visible:"#consent-modal"},{visible:"#privacy-settings-content"}]}],optIn:[{click:'button[type="submit"]'}],optOut:[{if:{exists:"#privacy-settings-content"},then:[{click:'button[type="submit"]'}],else:[{click:"div.one-modal__action-footer-column--secondary > a"}]}]},{name:"tropicfeel-com",prehideSelectors:["#shopify-section-cookies-controller"],detectCmp:[{exists:"#shopify-section-cookies-controller"}],detectPopup:[{visible:"#shopify-section-cookies-controller #cookies-controller-main-pane",check:"any"}],optIn:[{waitForThenClick:"#cookies-controller-main-pane form[data-form-allow-all] button"}],optOut:[{click:"#cookies-controller-main-pane a[data-tab-target=manage-cookies]"},{waitFor:"#manage-cookies-pane.active"},{click:"#manage-cookies-pane.active input[type=checkbox][checked]:not([disabled])",all:!0},{click:"#manage-cookies-pane.active button[type=submit]"}],test:[]},{name:"true-car",runContext:{urlPattern:"^https://www\\.truecar\\.com/"},cosmetic:!0,prehideSelectors:[['div[aria-labelledby="cookie-banner-heading"]']],detectCmp:[{exists:'div[aria-labelledby="cookie-banner-heading"]'}],detectPopup:[{visible:'div[aria-labelledby="cookie-banner-heading"]'}],optIn:[{click:'div[aria-labelledby="cookie-banner-heading"] > button[aria-label="Close"]'}],optOut:[{hide:'div[aria-labelledby="cookie-banner-heading"]'}]},{name:"truyo",prehideSelectors:["#truyo-consent-module"],detectCmp:[{exists:"#truyo-cookieBarContent"}],detectPopup:[{visible:"#truyo-consent-module"}],optIn:[{click:"button#acceptAllCookieButton"}],optOut:[{click:"button#declineAllCookieButton"}]},{name:"twitch-mobile",vendorUrl:"https://m.twitch.tv/",cosmetic:!0,runContext:{urlPattern:"^https?://m\\.twitch\\.tv"},prehideSelectors:[],detectCmp:[{exists:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],detectPopup:[{visible:'.ReactModal__Overlay [href="https://www.twitch.tv/p/cookie-policy"]'}],optIn:[{waitForThenClick:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"]) button'}],optOut:[{hide:'.ReactModal__Overlay:has([href="https://www.twitch.tv/p/cookie-policy"])'}]},{name:"twitch.tv",runContext:{urlPattern:"^https?://(www\\.)?twitch\\.tv"},prehideSelectors:["div:has(> .consent-banner .consent-banner__content--gdpr-v2),.ReactModalPortal:has([data-a-target=consent-modal-save])"],detectCmp:[{exists:".consent-banner .consent-banner__content--gdpr-v2"}],detectPopup:[{visible:".consent-banner .consent-banner__content--gdpr-v2"}],optIn:[{click:'button[data-a-target="consent-banner-accept"]'}],optOut:[{hide:"div:has(> .consent-banner .consent-banner__content--gdpr-v2)"},{click:'button[data-a-target="consent-banner-manage-preferences"]'},{waitFor:"input[type=checkbox][data-a-target=tw-checkbox]"},{click:"input[type=checkbox][data-a-target=tw-checkbox][checked]:not([disabled])",all:!0,optional:!0},{waitForThenClick:"[data-a-target=consent-modal-save]"},{waitForVisible:".ReactModalPortal:has([data-a-target=consent-modal-save])",check:"none"}]},{name:"twitter",runContext:{urlPattern:"^https://([a-z0-9-]+\\.)?twitter\\.com/"},prehideSelectors:['[data-testid="BottomBar"]'],detectCmp:[{exists:'[data-testid="BottomBar"] div'}],detectPopup:[{visible:'[data-testid="BottomBar"] div'}],optIn:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:first-child'}],optOut:[{waitForThenClick:'[data-testid="BottomBar"] > div:has(>div:first-child>div:last-child>span[role=button]) > div:last-child > div[role=button]:last-child'}],TODOtest:[{eval:"EVAL_document.cookie.includes('d_prefs=MjoxLGNvbnNlbnRfdmVyc2lvbjoy')"}]},{name:"ubuntu.com",prehideSelectors:["dialog.cookie-policy"],detectCmp:[{any:[{exists:"dialog.cookie-policy header"},{exists:'xpath///*[@id="modal"]/div/header'}]}],detectPopup:[{any:[{visible:"dialog header"},{visible:'xpath///*[@id="modal"]/div/header'}]}],optIn:[{any:[{waitForThenClick:"#cookie-policy-button-accept"},{waitForThenClick:'xpath///*[@id="cookie-policy-button-accept"]'}]}],optOut:[{any:[{waitForThenClick:"button.js-manage"},{waitForThenClick:'xpath///*[@id="cookie-policy-content"]/p[4]/button[2]'}]},{waitForThenClick:"dialog.cookie-policy .p-switch__input:checked",optional:!0,all:!0,timeout:500},{any:[{waitForThenClick:"dialog.cookie-policy .js-save-preferences"},{waitForThenClick:'xpath///*[@id="modal"]/div/button'}]}],test:[{eval:"EVAL_UBUNTU_COM_0"}]},{name:"UK Cookie Consent",prehideSelectors:["#catapult-cookie-bar"],cosmetic:!0,detectCmp:[{exists:"#catapult-cookie-bar"}],detectPopup:[{exists:".has-cookie-bar #catapult-cookie-bar"}],optIn:[{click:"#catapultCookie"}],optOut:[{hide:"#catapult-cookie-bar"}],test:[{eval:"EVAL_UK_COOKIE_CONSENT_0"}]},{name:"urbanarmorgear-com",cosmetic:!0,prehideSelectors:['div[class^="Layout__CookieBannerContainer-"]'],detectCmp:[{exists:'div[class^="Layout__CookieBannerContainer-"]'}],detectPopup:[{visible:'div[class^="Layout__CookieBannerContainer-"]'}],optIn:[{click:'button[class^="CookieBanner__AcceptButton"]'}],optOut:[{hide:'div[class^="Layout__CookieBannerContainer-"]'}]},{name:"usercentrics-api",detectCmp:[{exists:"#usercentrics-root"}],detectPopup:[{eval:"EVAL_USERCENTRICS_API_0"},{exists:["#usercentrics-root","[data-testid=uc-container]"]},{waitForVisible:"#usercentrics-root",timeout:2e3}],optIn:[{eval:"EVAL_USERCENTRICS_API_3"},{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_5"}],optOut:[{eval:"EVAL_USERCENTRICS_API_1"},{eval:"EVAL_USERCENTRICS_API_2"}],test:[{eval:"EVAL_USERCENTRICS_API_6"}]},{name:"usercentrics-button",detectCmp:[{exists:"#usercentrics-button"}],detectPopup:[{visible:"#usercentrics-button #uc-btn-accept-banner"}],optIn:[{click:"#usercentrics-button #uc-btn-accept-banner"}],optOut:[{click:"#usercentrics-button #uc-btn-deny-banner"}],test:[{eval:"EVAL_USERCENTRICS_BUTTON_0"}]},{name:"uswitch.com",prehideSelectors:["#cookie-banner-wrapper"],detectCmp:[{exists:"#cookie-banner-wrapper"}],detectPopup:[{visible:"#cookie-banner-wrapper"}],optIn:[{click:"#cookie_banner_accept_mobile"}],optOut:[{click:"#cookie_banner_save"}]},{name:"vodafone.de",runContext:{urlPattern:"^https://www\\.vodafone\\.de/"},prehideSelectors:[".dip-consent,.dip-consent-container"],detectCmp:[{exists:".dip-consent-container"}],detectPopup:[{visible:".dip-consent-content"}],optOut:[{click:'.dip-consent-btn[tabindex="2"]'}],optIn:[{click:'.dip-consent-btn[tabindex="1"]'}]},{name:"waitrose.com",prehideSelectors:["div[aria-labelledby=CookieAlertModalHeading]","section[data-test=initial-waitrose-cookie-consent-banner]","section[data-test=cookie-consent-modal]"],detectCmp:[{exists:"section[data-test=initial-waitrose-cookie-consent-banner]"}],detectPopup:[{visible:"section[data-test=initial-waitrose-cookie-consent-banner]"}],optIn:[{click:"button[data-test=accept-all]"}],optOut:[{click:"button[data-test=manage-cookies]"},{wait:200},{eval:"EVAL_WAITROSE_0"},{click:"button[data-test=submit]"}],test:[{eval:"EVAL_WAITROSE_1"}]},{name:"webflow",vendorUrl:"https://webflow.com/",prehideSelectors:[".fs-cc-components"],detectCmp:[{exists:".fs-cc-components"}],detectPopup:[{visible:".fs-cc-components"},{visible:"[fs-cc=banner]"}],optIn:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=allow]"}],optOut:[{wait:500},{waitForThenClick:"[fs-cc=banner] [fs-cc=deny]"}]},{name:"wetransfer.com",detectCmp:[{exists:".welcome__cookie-notice"}],detectPopup:[{visible:".welcome__cookie-notice"}],optIn:[{click:".welcome__button--accept"}],optOut:[{click:".welcome__button--decline"}]},{name:"whitepages.com",runContext:{urlPattern:"^https://www\\.whitepages\\.com/"},cosmetic:!0,prehideSelectors:[".cookie-wrapper, .cookie-overlay"],detectCmp:[{exists:".cookie-wrapper"}],detectPopup:[{visible:".cookie-overlay"}],optIn:[{click:'button[aria-label="Got it"]'}],optOut:[{hide:".cookie-wrapper"}]},{name:"wolframalpha",vendorUrl:"https://www.wolframalpha.com",prehideSelectors:[],cosmetic:!0,runContext:{urlPattern:"^https://www\\.wolframalpha\\.com/"},detectCmp:[{exists:"section._a_yb"}],detectPopup:[{visible:"section._a_yb"}],optIn:[{waitForThenClick:"section._a_yb button"}],optOut:[{hide:"section._a_yb"}]},{name:"woo-commerce-com",prehideSelectors:[".wccom-comp-privacy-banner .wccom-privacy-banner"],detectCmp:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],detectPopup:[{exists:".wccom-comp-privacy-banner .wccom-privacy-banner"}],optIn:[{click:".wccom-privacy-banner__content-buttons button.is-primary"}],optOut:[{click:".wccom-privacy-banner__content-buttons button.is-secondary"},{waitForThenClick:"input[type=checkbox][checked]:not([disabled])",all:!0},{click:"div.wccom-modal__footer > button"}]},{name:"WP Cookie Notice for GDPR",vendorUrl:"https://wordpress.org/plugins/gdpr-cookie-consent/",prehideSelectors:["#gdpr-cookie-consent-bar"],detectCmp:[{exists:"#gdpr-cookie-consent-bar"}],detectPopup:[{visible:"#gdpr-cookie-consent-bar"}],optIn:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_accept"}],optOut:[{waitForThenClick:"#gdpr-cookie-consent-bar #cookie_action_reject"}],test:[{eval:"EVAL_WP_COOKIE_NOTICE_0"}]},{name:"wpcc",cosmetic:!0,prehideSelectors:[".wpcc-container"],detectCmp:[{exists:".wpcc-container"}],detectPopup:[{exists:".wpcc-container .wpcc-message"}],optIn:[{click:".wpcc-compliance .wpcc-btn"}],optOut:[{hide:".wpcc-container"}]},{name:"xe.com",vendorUrl:"https://www.xe.com/",runContext:{urlPattern:"^https://www\\.xe\\.com/"},prehideSelectors:["[class*=ConsentBanner]"],detectCmp:[{exists:"[class*=ConsentBanner]"}],detectPopup:[{visible:"[class*=ConsentBanner]"}],optIn:[{waitForThenClick:"[class*=ConsentBanner] .egnScw"}],optOut:[{wait:1e3},{waitForThenClick:"[class*=ConsentBanner] .frDWEu"},{waitForThenClick:"[class*=ConsentBanner] .hXIpFU"}],test:[{eval:"EVAL_XE_TEST"}]},{name:"xhamster-eu",prehideSelectors:[".cookies-modal"],detectCmp:[{exists:".cookies-modal"}],detectPopup:[{exists:".cookies-modal"}],optIn:[{click:"button.cmd-button-accept-all"}],optOut:[{click:"button.cmd-button-reject-all"}]},{name:"xhamster-us",runContext:{urlPattern:"^https://(www\\.)?xhamster\\d?\\.com"},cosmetic:!0,prehideSelectors:[".cookie-announce"],detectCmp:[{exists:".cookie-announce"}],detectPopup:[{visible:".cookie-announce .announce-text"}],optIn:[{click:".cookie-announce button.xh-button"}],optOut:[{hide:".cookie-announce"}]},{name:"xing.com",detectCmp:[{exists:"div[class^=cookie-consent-CookieConsent]"}],detectPopup:[{exists:"div[class^=cookie-consent-CookieConsent]"}],optIn:[{click:"#consent-accept-button"}],optOut:[{click:"#consent-settings-button"},{click:".consent-banner-button-accept-overlay"}],test:[{eval:"EVAL_XING_0"}]},{name:"xnxx-com",cosmetic:!0,prehideSelectors:["#cookies-use-alert"],detectCmp:[{exists:"#cookies-use-alert"}],detectPopup:[{visible:"#cookies-use-alert"}],optIn:[{click:"#cookies-use-alert .close"}],optOut:[{hide:"#cookies-use-alert"}]},{name:"xvideos",vendorUrl:"https://xvideos.com",runContext:{urlPattern:"^https://[^/]*xvideos\\.com/"},prehideSelectors:[],detectCmp:[{exists:".disclaimer-opened #disclaimer-cookies"}],detectPopup:[{visible:".disclaimer-opened #disclaimer-cookies"}],optIn:[{waitForThenClick:"#disclaimer-accept_cookies"}],optOut:[{waitForThenClick:"#disclaimer-reject_cookies"}]},{name:"Yahoo",runContext:{urlPattern:"^https://consent\\.yahoo\\.com/v2/"},prehideSelectors:["#reject-all"],detectCmp:[{exists:"#consent-page"}],detectPopup:[{visible:"#consent-page"}],optIn:[{waitForThenClick:"#consent-page button[value=agree]"}],optOut:[{waitForThenClick:"#consent-page button[value=reject]"}]},{name:"youporn.com",cosmetic:!0,prehideSelectors:[".euCookieModal, #js_euCookieModal"],detectCmp:[{exists:".euCookieModal"}],detectPopup:[{exists:".euCookieModal, #js_euCookieModal"}],optIn:[{click:'button[name="user_acceptCookie"]'}],optOut:[{hide:".euCookieModal"}]},{name:"youtube-desktop",prehideSelectors:["tp-yt-iron-overlay-backdrop.opened","ytd-consent-bump-v2-lightbox"],detectCmp:[{exists:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"},{exists:'ytd-consent-bump-v2-lightbox tp-yt-paper-dialog a[href^="https://consent.youtube.com/"]'}],detectPopup:[{visible:"ytd-consent-bump-v2-lightbox tp-yt-paper-dialog"}],optIn:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:last-child button"},{wait:500}],optOut:[{waitForThenClick:"ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child #button,ytd-consent-bump-v2-lightbox .eom-buttons .eom-button-row:first-child ytd-button-renderer:first-child button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_DESKTOP_0"}]},{name:"youtube-mobile",prehideSelectors:[".consent-bump-v2-lightbox"],detectCmp:[{exists:"ytm-consent-bump-v2-renderer"}],detectPopup:[{visible:"ytm-consent-bump-v2-renderer"}],optIn:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:first-child button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:first-child button"},{wait:500}],optOut:[{waitForThenClick:"ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons c3-material-button:nth-child(2) button, ytm-consent-bump-v2-renderer .privacy-terms + .one-col-dialog-buttons ytm-button-renderer:nth-child(2) button"},{wait:500}],test:[{wait:500},{eval:"EVAL_YOUTUBE_MOBILE_0"}]},{name:"zdf",prehideSelectors:["#zdf-cmp-banner-sdk"],detectCmp:[{exists:"#zdf-cmp-banner-sdk"}],detectPopup:[{visible:"#zdf-cmp-main.zdf-cmp-show"}],optIn:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-accept-btn"}],optOut:[{waitForThenClick:"#zdf-cmp-main #zdf-cmp-deny-btn"}],test:[]}],C={"didomi.io":{detectors:[{presentMatcher:{target:{selector:"#didomi-host, #didomi-notice"},type:"css"},showingMatcher:{target:{selector:"body.didomi-popup-open, .didomi-notice-banner"},type:"css"}}],methods:[{action:{target:{selector:".didomi-popup-notice-buttons .didomi-button:not(.didomi-button-highlight), .didomi-notice-banner .didomi-learn-more-button"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{retries:50,target:{selector:"#didomi-purpose-cookies"},type:"waitcss",waitTime:50},{consents:[{description:"Share (everything) with others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-share_whith_others]:last-child"},type:"click"},type:"X"},{description:"Information storage and access",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-cookies]:last-child"},type:"click"},type:"D"},{description:"Content selection, offers and marketing",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-CL-T1Rgm7]:last-child"},type:"click"},type:"E"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-analytics]:last-child"},type:"click"},type:"B"},{description:"Analytics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-M9NRHJe3G]:last-child"},type:"click"},type:"B"},{description:"Ad and content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-advertising_personalization]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection",falseAction:{parent:{childFilter:{target:{selector:"#didomi-purpose-pub-ciblee"}},selector:".didomi-consent-popup-data-processing, .didomi-components-accordion-label-container"},target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-pub-ciblee]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - basics",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-q4zlJqdcD]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - partners and subsidiaries",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-partenaire-cAsDe8jC]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-p4em9a8m]:last-child"},type:"click"},type:"F"},{description:"Ad and content selection - others",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-autres-pub]:last-child"},type:"click"},type:"F"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-reseauxsociaux]:last-child"},type:"click"},type:"A"},{description:"Social networks",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-social_media]:last-child"},type:"click"},type:"A"},{description:"Content selection",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-content_personalization]:last-child"},type:"click"},type:"E"},{description:"Ad delivery",falseAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:first-child"},type:"click"},trueAction:{target:{selector:".didomi-components-radio__option[aria-describedby=didomi-purpose-ad_delivery]:last-child"},type:"click"},type:"F"}],type:"consent"},{action:{consents:[{matcher:{childFilter:{target:{selector:":not(.didomi-components-radio__option--selected)"}},type:"css"},trueAction:{target:{selector:":nth-child(2)"},type:"click"},falseAction:{target:{selector:":first-child"},type:"click"},type:"X"}],type:"consent"},target:{selector:".didomi-components-radio"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".didomi-consent-popup-footer .didomi-consent-popup-actions"},target:{selector:".didomi-components-button:first-child"},type:"click"},name:"SAVE_CONSENT"}]},oil:{detectors:[{presentMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"},showingMatcher:{target:{selector:".as-oil-content-overlay"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".as-js-advanced-settings"},type:"click"},{retries:"10",target:{selector:".as-oil-cpc__purpose-container"},type:"waitcss",waitTime:"250"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{consents:[{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Information storage and access","Opbevaring af og adgang til oplysninger på din enhed"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"D"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personlige annoncer","Personalisation"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Annoncevalg, levering og rapportering","Ad selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:["Personalisering af indhold","Content selection, delivery, reporting"]},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"E"},{matcher:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{childFilter:{target:{selector:".as-oil-cpc__purpose-header",textFilter:["Måling","Measurement"]}},selector:".as-oil-cpc__purpose-container"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"B"},{matcher:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".as-oil-cpc__purpose-container",textFilter:"Google"},target:{selector:".as-oil-cpc__switch"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:".as-oil__btn-optin"},type:"click"},name:"SAVE_CONSENT"},{action:{target:{selector:"div.as-oil"},type:"hide"},name:"HIDE_CMP"}]},optanon:{detectors:[{presentMatcher:{target:{selector:"#optanon-menu, .optanon-alert-box-wrapper"},type:"css"},showingMatcher:{target:{displayFilter:!0,selector:".optanon-alert-box-wrapper"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".optanon-alert-box-wrapper .optanon-toggle-display, a[onclick*='OneTrust.ToggleInfoDisplay()'], a[onclick*='Optanon.ToggleInfoDisplay()']"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".preference-menu-item #Your-privacy"},type:"click"},{target:{selector:"#optanon-vendor-consent-text"},type:"click"},{action:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},target:{selector:"#optanon-vendor-consent-list .vendor-item"},type:"foreach"},{target:{selector:".vendor-consent-back-link"},type:"click"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-performance"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-functional"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-advertising"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-social"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Social Media Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalisation"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Site monitoring cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Third party privacy-enhanced content"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Performance & Advertising Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Information storage and access"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"D"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content selection, delivery, reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Measurement"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Recommended Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Unclassified Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"X"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Analytical Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"B"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Marketing Cookies"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Personalization"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Ad Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},type:"ifcss"},{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},trueAction:{actions:[{parent:{selector:"#optanon-menu, .optanon-menu"},target:{selector:".menu-item-necessary",textFilter:"Content Selection, Delivery & Reporting"},type:"click"},{consents:[{matcher:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status input"},type:"checkbox"},toggleAction:{parent:{selector:"#optanon-popup-body-right"},target:{selector:".optanon-status label"},type:"click"},type:"E"}],type:"consent"}],type:"list"},type:"ifcss"}],type:"list"},name:"DO_CONSENT"},{action:{parent:{selector:".optanon-save-settings-button"},target:{selector:".optanon-white-button-middle"},type:"click"},name:"SAVE_CONSENT"},{action:{actions:[{target:{selector:"#optanon-popup-wrapper"},type:"hide"},{target:{selector:"#optanon-popup-bg"},type:"hide"},{target:{selector:".optanon-alert-box-wrapper"},type:"hide"}],type:"list"},name:"HIDE_CMP"}]},quantcast2:{detectors:[{presentMatcher:{target:{selector:"[data-tracking-opt-in-overlay]"},type:"css"},showingMatcher:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"css"}}],methods:[{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-learn-more]"},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{type:"wait",waitTime:500},{action:{actions:[{target:{selector:"div",textFilter:["Information storage and access"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"D"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Personalization"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Ad selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"F"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Content selection, delivery, reporting"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"E"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Measurement"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"B"}],type:"consent"},type:"ifcss"},{target:{selector:"div",textFilter:["Other Partners"]},trueAction:{consents:[{matcher:{target:{selector:"input"},type:"checkbox"},toggleAction:{target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},type:"ifcss"}],type:"list"},parent:{childFilter:{target:{selector:"input"}},selector:"[data-tracking-opt-in-overlay] > div > div"},target:{childFilter:{target:{selector:"input"}},selector:":scope > div"},type:"foreach"}],type:"list"},name:"DO_CONSENT"},{action:{target:{selector:"[data-tracking-opt-in-overlay] [data-tracking-opt-in-save]"},type:"click"},name:"SAVE_CONSENT"}]},springer:{detectors:[{presentMatcher:{parent:null,target:{selector:".cmp-app_gdpr"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".cmp-popup_popup"},type:"css"}}],methods:[{action:{actions:[{target:{selector:".cmp-intro_rejectAll"},type:"click"},{type:"wait",waitTime:250},{target:{selector:".cmp-purposes_purposeItem:not(.cmp-purposes_selectedPurpose)"},type:"click"}],type:"list"},name:"OPEN_OPTIONS"},{action:{consents:[{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Przechowywanie informacji na urządzeniu lub dostęp do nich",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"D"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór podstawowych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"F"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Tworzenie profilu spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"E"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Wybór spersonalizowanych treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności reklam",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Pomiar wydajności treści",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"B"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Stosowanie badań rynkowych w celu generowania opinii odbiorców",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"},{matcher:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch .cmp-switch_isSelected"},type:"css"},toggleAction:{parent:{selector:".cmp-purposes_detailHeader",textFilter:"Opracowywanie i ulepszanie produktów",childFilter:{target:{selector:".cmp-switch_switch"}}},target:{selector:".cmp-switch_switch:not(.cmp-switch_isSelected)"},type:"click"},type:"X"}],type:"consent"},name:"DO_CONSENT"},{action:{target:{selector:".cmp-details_save"},type:"click"},name:"SAVE_CONSENT"}]},wordpressgdpr:{detectors:[{presentMatcher:{parent:null,target:{selector:".wpgdprc-consent-bar"},type:"css"},showingMatcher:{parent:null,target:{displayFilter:!0,selector:".wpgdprc-consent-bar"},type:"css"}}],methods:[{action:{parent:null,target:{selector:".wpgdprc-consent-bar .wpgdprc-consent-bar__settings",textFilter:null},type:"click"},name:"OPEN_OPTIONS"},{action:{actions:[{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Eyeota"},type:"click"},{consents:[{description:"Eyeota Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Eyeota"},target:{selector:"label"},type:"click"},type:"X"}],type:"consent"},{target:{selector:".wpgdprc-consent-modal .wpgdprc-button",textFilter:"Advertising"},type:"click"},{consents:[{description:"Advertising Cookies",matcher:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"input"},type:"checkbox"},toggleAction:{parent:{selector:".wpgdprc-consent-modal__description",textFilter:"Advertising"},target:{selector:"label"},type:"click"},type:"F"}],type:"consent"}],type:"list"},name:"DO_CONSENT"},{action:{parent:null,target:{selector:".wpgdprc-button",textFilter:"Save my settings"},type:"click"},name:"SAVE_CONSENT"}]}},v={autoconsent:w,consentomatic:C},f=Object.freeze({__proto__:null,autoconsent:w,consentomatic:C,default:v});const A=new class{constructor(e,t=null,o=null){if(this.id=n(),this.rules=[],this.foundCmp=null,this.state={lifecycle:"loading",prehideOn:!1,findCmpAttempts:0,detectedCmps:[],detectedPopups:[],selfTest:null},a.sendContentMessage=e,this.sendContentMessage=e,this.rules=[],this.updateState({lifecycle:"loading"}),this.addDynamicRules(),t)this.initialize(t,o);else{o&&this.parseDeclarativeRules(o);e({type:"init",url:window.location.href}),this.updateState({lifecycle:"waitingForInitResponse"})}this.domActions=new class{constructor(e){this.autoconsentInstance=e}click(e,t=!1){const o=this.elementSelector(e);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[click]",e,t,o),o.length>0&&(t?o.forEach((e=>e.click())):o[0].click()),o.length>0}elementExists(e){return this.elementSelector(e).length>0}elementVisible(e,t){const o=this.elementSelector(e),c=new Array(o.length);return o.forEach(((e,t)=>{c[t]=k(e)})),"none"===t?c.every((e=>!e)):0!==c.length&&("any"===t?c.some((e=>e)):c.every((e=>e)))}waitForElement(e,t=1e4){const o=Math.ceil(t/200);return this.autoconsentInstance.config.logs.rulesteps&&console.log("[waitForElement]",e),h((()=>this.elementSelector(e).length>0),o,200)}waitForVisible(e,t=1e4,o="any"){return h((()=>this.elementVisible(e,o)),Math.ceil(t/200),200)}async waitForThenClick(e,t=1e4,o=!1){return await this.waitForElement(e,t),this.click(e,o)}wait(e){return new Promise((t=>{setTimeout((()=>{t(!0)}),e)}))}hide(e,t){return m(u(),e,t)}prehide(e){const t=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[prehide]",t,location.href),m(t,e,"opacity")}undoPrehide(){const e=u("autoconsent-prehide");return this.autoconsentInstance.config.logs.lifecycle&&console.log("[undoprehide]",e,location.href),e&&e.remove(),!!e}querySingleReplySelector(e,t=document){if(e.startsWith("aria/"))return[];if(e.startsWith("xpath/")){const o=e.slice(6),c=document.evaluate(o,t,null,XPathResult.ANY_TYPE,null);let i=null;const n=[];for(;i=c.iterateNext();)n.push(i);return n}return e.startsWith("text/")||e.startsWith("pierce/")?[]:t.shadowRoot?Array.from(t.shadowRoot.querySelectorAll(e)):Array.from(t.querySelectorAll(e))}querySelectorChain(e){let t,o=document;for(const c of e){if(t=this.querySingleReplySelector(c,o),0===t.length)return[];o=t[0]}return t}elementSelector(e){return"string"==typeof e?this.querySingleReplySelector(e):this.querySelectorChain(e)}}(this)}initialize(e,t){const o=b(e);if(o.logs.lifecycle&&console.log("autoconsent init",window.location.href),this.config=o,o.enabled){if(t&&this.parseDeclarativeRules(t),this.rules=function(e,t){return e.filter((e=>(!t.disabledCmps||!t.disabledCmps.includes(e.name))&&(t.enableCosmeticRules||!e.isCosmetic)))}(this.rules,o),e.enablePrehide)if(document.documentElement)this.prehideElements();else{const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.prehideElements()};window.addEventListener("DOMContentLoaded",e)}if("loading"===document.readyState){const e=()=>{window.removeEventListener("DOMContentLoaded",e),this.start()};window.addEventListener("DOMContentLoaded",e)}else this.start();this.updateState({lifecycle:"initialized"})}else o.logs.lifecycle&&console.log("autoconsent is disabled")}addDynamicRules(){y.forEach((e=>{this.rules.push(new e(this))}))}parseDeclarativeRules(e){Object.keys(e.consentomatic).forEach((t=>{this.addConsentomaticCMP(t,e.consentomatic[t])})),e.autoconsent.forEach((e=>{this.addDeclarativeCMP(e)}))}addDeclarativeCMP(e){this.rules.push(new d(e,this))}addConsentomaticCMP(e,t){this.rules.push(new class{constructor(e,t){this.name=e,this.config=t,this.methods=new Map,this.runContext=l,this.isCosmetic=!1,t.methods.forEach((e=>{e.action&&this.methods.set(e.name,e.action)})),this.hasSelfTest=!1}get isIntermediate(){return!1}checkRunContext(){return!0}async detectCmp(){return this.config.detectors.map((e=>o(e.presentMatcher))).some((e=>!!e))}async detectPopup(){return this.config.detectors.map((e=>o(e.showingMatcher))).some((e=>!!e))}async executeAction(e,t){return!this.methods.has(e)||c(this.methods.get(e),t)}async optOut(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",[]),await this.executeAction("SAVE_CONSENT"),!0}async optIn(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),await this.executeAction("HIDE_CMP"),await this.executeAction("DO_CONSENT",["D","A","B","E","F","X"]),await this.executeAction("SAVE_CONSENT"),!0}async openCmp(){return await this.executeAction("HIDE_CMP"),await this.executeAction("OPEN_OPTIONS"),!0}async test(){return!0}}(`com_${e}`,t))}start(){window.requestIdleCallback?window.requestIdleCallback((()=>this._start()),{timeout:500}):this._start()}async _start(){const e=this.config.logs;e.lifecycle&&console.log(`Detecting CMPs on ${window.location.href}`),this.updateState({lifecycle:"started"});const t=await this.findCmp(this.config.detectRetries);if(this.updateState({detectedCmps:t.map((e=>e.name))}),0===t.length)return e.lifecycle&&console.log("no CMP found",location.href),this.config.enablePrehide&&this.undoPrehide(),this.updateState({lifecycle:"nothingDetected"}),!1;this.updateState({lifecycle:"cmpDetected"});const o=[],c=[];for(const e of t)e.isCosmetic?c.push(e):o.push(e);let i=!1,n=await this.detectPopups(o,(async e=>{i=await this.handlePopup(e)}));if(0===n.length&&(n=await this.detectPopups(c,(async e=>{i=await this.handlePopup(e)}))),0===n.length)return e.lifecycle&&console.log("no popup found"),this.config.enablePrehide&&this.undoPrehide(),!1;if(n.length>1){const t={msg:"Found multiple CMPs, check the detection rules.",cmps:n.map((e=>e.name))};e.errors&&console.warn(t.msg,t.cmps),this.sendContentMessage({type:"autoconsentError",details:t})}return i}async findCmp(e){const t=this.config.logs;this.updateState({findCmpAttempts:this.state.findCmpAttempts+1});const o=[];for(const e of this.rules)try{if(!e.checkRunContext())continue;await e.detectCmp()&&(t.lifecycle&&console.log(`Found CMP: ${e.name} ${window.location.href}`),this.sendContentMessage({type:"cmpDetected",url:location.href,cmp:e.name}),o.push(e))}catch(o){t.errors&&console.warn(`error detecting ${e.name}`,o)}return 0===o.length&&e>0?(await this.domActions.wait(500),this.findCmp(e-1)):o}async detectPopup(e){if(await this.waitForPopup(e).catch((t=>(this.config.logs.errors&&console.warn(`error waiting for a popup for ${e.name}`,t),!1))))return this.updateState({detectedPopups:this.state.detectedPopups.concat([e.name])}),this.sendContentMessage({type:"popupFound",cmp:e.name,url:location.href}),e;throw new Error("Popup is not shown")}async detectPopups(e,t){const o=e.map((e=>this.detectPopup(e)));await Promise.any(o).then((e=>{t(e)})).catch((()=>null));const c=await Promise.allSettled(o),i=[];for(const e of c)"fulfilled"===e.status&&i.push(e.value);return i}async handlePopup(e){return this.updateState({lifecycle:"openPopupDetected"}),this.config.enablePrehide&&!this.state.prehideOn&&this.prehideElements(),this.foundCmp=e,"optOut"===this.config.autoAction?await this.doOptOut():"optIn"===this.config.autoAction?await this.doOptIn():(this.config.logs.lifecycle&&console.log("waiting for opt-out signal...",location.href),!0)}async doOptOut(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptOut"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt out on ${window.location.href}`),t=await this.foundCmp.optOut(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt out result ${t}`)):(e.errors&&console.log("no CMP to opt out"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optOutResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:this.foundCmp&&this.foundCmp.hasSelfTest,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optOutSucceeded":"optOutFailed"}),t}async doOptIn(){const e=this.config.logs;let t;return this.updateState({lifecycle:"runningOptIn"}),this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: opt in on ${window.location.href}`),t=await this.foundCmp.optIn(),e.lifecycle&&console.log(`${this.foundCmp.name}: opt in result ${t}`)):(e.errors&&console.log("no CMP to opt in"),t=!1),this.config.enablePrehide&&this.undoPrehide(),this.sendContentMessage({type:"optInResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,scheduleSelfTest:!1,url:location.href}),t&&!this.foundCmp.isIntermediate?(this.sendContentMessage({type:"autoconsentDone",cmp:this.foundCmp.name,isCosmetic:this.foundCmp.isCosmetic,url:location.href}),this.updateState({lifecycle:"done"})):this.updateState({lifecycle:t?"optInSucceeded":"optInFailed"}),t}async doSelfTest(){const e=this.config.logs;let t;return this.foundCmp?(e.lifecycle&&console.log(`CMP ${this.foundCmp.name}: self-test on ${window.location.href}`),t=await this.foundCmp.test()):(e.errors&&console.log("no CMP to self test"),t=!1),this.sendContentMessage({type:"selfTestResult",cmp:this.foundCmp?this.foundCmp.name:"none",result:t,url:location.href}),this.updateState({selfTest:t}),t}async waitForPopup(e,t=5,o=500){const c=this.config.logs;c.lifecycle&&console.log("checking if popup is open...",e.name);const i=await e.detectPopup().catch((t=>(c.errors&&console.warn(`error detecting popup for ${e.name}`,t),!1)));return!i&&t>0?(await this.domActions.wait(o),this.waitForPopup(e,t-1,o)):(c.lifecycle&&console.log(e.name,"popup is "+(i?"open":"not open")),i)}prehideElements(){const e=this.config.logs,t=this.rules.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=a.pending.get(e);o?(a.pending.delete(e),o.timer&&window.clearTimeout(o.timer),o.resolve(t)):console.warn("no eval #",e)}(e.id,e.result)}}}((e=>{window.webkit.messageHandlers[e.type]&&window.webkit.messageHandlers[e.type].postMessage(e).then((e=>{A.receiveMessageCallback(e)}))}),null,f);window.autoconsentMessageCallback=e=>{A.receiveMessageCallback(e)}}(); diff --git a/package-lock.json b/package-lock.json index a506b40d5d..b3d356236a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "macos-browser", "version": "1.0.0", "dependencies": { - "@duckduckgo/autoconsent": "^10.3.0" + "@duckduckgo/autoconsent": "^10.5.0" }, "devDependencies": { "@rollup/plugin-json": "^4.1.0", @@ -53,9 +53,9 @@ } }, "node_modules/@duckduckgo/autoconsent": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.3.0.tgz", - "integrity": "sha512-dUf37qkaYDuXEytU9mNNLGw28S1t1M1dFnvMHZDV9BpINVJeAl1ye7CmlABuGlDs6URrp2ZLZ5IxcKQhQglYcw==" + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/@duckduckgo/autoconsent/-/autoconsent-10.5.0.tgz", + "integrity": "sha512-4mdp9mwBiE+IKTvN84iRA8d7eSkJ5xMaQvhvbgw7XlD1VOJlfiJPhP8PJWV+wyc7DNVHMtcdUXiD+ICw/SJBRA==" }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.2", diff --git a/package.json b/package.json index ee09d66203..dbae81bb89 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,6 @@ "rollup-plugin-terser": "^7.0.2" }, "dependencies": { - "@duckduckgo/autoconsent": "^10.3.0" + "@duckduckgo/autoconsent": "^10.5.0" } } From 8989cfaec9f5b2b79c21c90dc1e82225ec0480b1 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 9 Apr 2024 14:35:00 +0500 Subject: [PATCH 05/40] keep Downloads button when opened using Cmd+J (#2577) Task/Issue URL: https://app.asana.com/0/1199230911884351/1206968292746694/f --- DuckDuckGo/Menus/MainMenuActions.swift | 2 +- DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 171e6699c1..4f2befbe8b 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -392,7 +392,7 @@ extension MainViewController { } navigationBarViewController.view.window?.makeKeyAndOrderFront(nil) } - navigationBarViewController.toggleDownloadsPopover(keepButtonVisible: false) + navigationBarViewController.toggleDownloadsPopover(keepButtonVisible: sender is NSMenuItem /* keep button visible for some time on Cmd+J */) } @objc func toggleBookmarksBarFromMenu(_ sender: Any) { diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index edd999e760..950cdb4056 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -1028,7 +1028,7 @@ extension NavigationBarViewController: OptionsButtonMenuDelegate { } func optionsButtonMenuRequestedDownloadsPopover(_ menu: NSMenu) { - toggleDownloadsPopover(keepButtonVisible: false) + toggleDownloadsPopover(keepButtonVisible: true) } func optionsButtonMenuRequestedPrint(_ menu: NSMenu) { From dff594e4f16f62cb5d2c964b68fbda20a7abc464 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Tue, 9 Apr 2024 14:50:07 +0500 Subject: [PATCH 06/40] Fix download popup appearing on launch (#2578) Task/Issue URL: https://app.asana.com/0/0/1207025976231577/f --- .../Services/DownloadListCoordinator.swift | 13 ++++---- .../View/NavigationBarViewController.swift | 24 +++++++------- .../DownloadListCoordinatorTests.swift | 33 ++++++++++++------- 3 files changed, 39 insertions(+), 31 deletions(-) diff --git a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift index a7d0b3e1d3..5e9c9e14c9 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift @@ -54,7 +54,7 @@ final class DownloadListCoordinator { enum UpdateKind { case added case removed - case updated + case updated(oldValue: DownloadListItem) } typealias Update = (kind: UpdateKind, item: DownloadListItem) private let updatesSubject = PassthroughSubject() @@ -379,8 +379,8 @@ final class DownloadListCoordinator { case (.none, .some(let item)): self.updatesSubject.send((.added, item)) store.save(item) - case (.some, .some(let item)): - self.updatesSubject.send((.updated, item)) + case (.some(let oldValue), .some(let item)): + self.updatesSubject.send((.updated(oldValue: oldValue), item)) store.save(item) case (.some(let item), .none): item.progress?.cancel() @@ -446,10 +446,9 @@ final class DownloadListCoordinator { @MainActor func downloads(sortedBy keyPath: KeyPath, ascending: Bool) -> [DownloadListItem] { - let comparator: (T, T) -> Bool = ascending ? (<) : (>) - return items.values.sorted(by: { - comparator($0[keyPath: keyPath], $1[keyPath: keyPath]) - }) + return items.values.sorted { + ascending ? ($0[keyPath: keyPath] < $1[keyPath: keyPath]) : ($0[keyPath: keyPath] > $1[keyPath: keyPath]) + } } var updates: AnyPublisher { diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 950cdb4056..28f197478c 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -636,22 +636,20 @@ final class NavigationBarViewController: NSViewController { .sink { [weak self] update in guard let self else { return } - let shouldShowPopover = update.kind == .updated - && DownloadsPreferences.shared.shouldOpenPopupOnCompletion - && update.item.destinationURL != nil - && update.item.tempURL == nil - && !update.item.isBurner - && WindowControllersManager.shared.lastKeyMainWindowController?.window === downloadsButton.window - - if shouldShowPopover { + if case .updated(let oldValue) = update.kind, + DownloadsPreferences.shared.shouldOpenPopupOnCompletion, + update.item.destinationURL != nil, + update.item.tempURL == nil, + oldValue.tempURL != nil, // download finished + !update.item.isBurner, + WindowControllersManager.shared.lastKeyMainWindowController?.window === downloadsButton.window { + self.popovers.showDownloadsPopoverAndAutoHide(usingView: downloadsButton, popoverDelegate: self, downloadsDelegate: self) - } else { - if update.item.isBurner { - invalidateDownloadButtonHidingTimer() - updateDownloadsButton(updatingFromPinnedViewsNotification: false) - } + } else if update.item.isBurner { + invalidateDownloadButtonHidingTimer() + updateDownloadsButton(updatingFromPinnedViewsNotification: false) } updateDownloadsButton() } diff --git a/UnitTests/FileDownload/DownloadListCoordinatorTests.swift b/UnitTests/FileDownload/DownloadListCoordinatorTests.swift index 1092aac187..8446b932b2 100644 --- a/UnitTests/FileDownload/DownloadListCoordinatorTests.swift +++ b/UnitTests/FileDownload/DownloadListCoordinatorTests.swift @@ -75,7 +75,7 @@ final class DownloadListCoordinatorTests: XCTestCase { let e = expectation(description: "download added") var id: UUID! let c = coordinator.updates.sink { kind, item in - if kind == .added { + if case .added = kind { e.fulfill() id = item.identifier } @@ -153,9 +153,10 @@ final class DownloadListCoordinatorTests: XCTestCase { } let c = coordinator.updates.sink { (kind, item) in - if kind == .added { + if case .added = kind { expectations[item.identifier]!.fulfill() - } else if kind != .updated { + } else if case .updated = kind { + } else { XCTFail("unexpected \(kind) \(item.fileName)") } } @@ -217,7 +218,7 @@ final class DownloadListCoordinatorTests: XCTestCase { let taskCompleted = expectation(description: "item updated") var c: AnyCancellable! c = coordinator.updates.sink { (kind, item) in - guard kind == .updated, item.progress == nil else { return } + guard case .updated = kind, item.progress == nil else { return } taskCompleted.fulfill() @@ -267,7 +268,9 @@ final class DownloadListCoordinatorTests: XCTestCase { let taskCompleted = expectation(description: "location updated") var c: AnyCancellable! c = coordinator.updates.sink { (kind, item) in - XCTAssertEqual(kind, .updated) + if case .updated = kind { } else { + XCTFail("\(kind) is not .updated") + } guard item.destinationURL != nil, item.tempURL != nil else { return } XCTAssertEqual(item.destinationURL, self.destURL) @@ -328,7 +331,9 @@ final class DownloadListCoordinatorTests: XCTestCase { let itemUpdated = expectation(description: "item updated") let c = coordinator.updates.sink { (kind, item) in - XCTAssertEqual(kind, .updated) + if case .updated = kind { } else { + XCTFail("\(kind) is not .updated") + } itemUpdated.fulfill() XCTAssertEqual(item.destinationURL, item.destinationURL) @@ -386,7 +391,9 @@ final class DownloadListCoordinatorTests: XCTestCase { let itemUpdated = expectation(description: "item updated") let c = coordinator.updates.sink { (kind, item) in - XCTAssertEqual(kind, .updated) + if case .updated = kind { } else { + XCTFail("\(kind) is not .updated") + } itemUpdated.fulfill() XCTAssertEqual(item.destinationURL, item.destinationURL) @@ -444,7 +451,9 @@ final class DownloadListCoordinatorTests: XCTestCase { let itemUpdated = expectation(description: "item updated") let c = coordinator.updates.sink { (kind, item) in - XCTAssertEqual(kind, .updated) + if case .updated = kind { } else { + XCTFail("\(kind) is not .updated") + } itemUpdated.fulfill() XCTAssertEqual(item.destinationURL, item.destinationURL) @@ -467,7 +476,9 @@ final class DownloadListCoordinatorTests: XCTestCase { let itemRemoved = expectation(description: "item removed") let c = coordinator.updates.sink { (kind, item) in - XCTAssertEqual(kind, .removed) + if case .removed = kind { } else { + XCTFail("\(kind) is not .updated") + } itemRemoved.fulfill() XCTAssertEqual(item.identifier, id) @@ -499,7 +510,7 @@ final class DownloadListCoordinatorTests: XCTestCase { let e1 = expectation(description: "download stopped") e1.expectedFulfillmentCount = 2 var c = coordinator.updates.sink { (kind, item) in - guard kind == .updated, item.progress == nil else { return } + guard case .updated = kind, item.progress == nil else { return } e1.fulfill() XCTAssertNotEqual(item.identifier, keptId) } @@ -509,7 +520,7 @@ final class DownloadListCoordinatorTests: XCTestCase { let e2 = expectation(description: "item removed") e2.expectedFulfillmentCount = 2 c = coordinator.updates.sink { (kind, item) in - guard kind == .removed else { return } + guard case .removed = kind else { return } e2.fulfill() XCTAssertNotEqual(item.identifier, keptId) } From a0819ab2784775240732e0dbda2238074087ab01 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 9 Apr 2024 12:30:21 +0200 Subject: [PATCH 07/40] Fixes some IPC issues that were causing high CPU usage (#2582) Task/Issue URL: https://app.asana.com/0/1177771139624306/1207034395541856/f ## Description Fixes some issues with IPC causing CPU usage to spike up considerably. --- .../NetworkProtectionNavBarButtonModel.swift | 8 ++++--- .../Model/PreferencesSidebarModel.swift | 13 ++++++++---- .../View/PrivacyDashboardViewController.swift | 2 +- DuckDuckGo/Tab/Model/Tab.swift | 21 ++++++------------- .../NetworkProtectionFeatureVisibility.swift | 2 ++ UnitTests/Menus/MoreOptionsMenuTests.swift | 8 +++++-- 6 files changed, 29 insertions(+), 25 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift index d8d1aaedf2..ee8b4d550b 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionNavBarButtonModel.swift @@ -36,8 +36,9 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { private var cancellables = Set() - // MARK: - NetP Icon publisher + // MARK: - VPN + private let vpnVisibility: NetworkProtectionFeatureVisibility private let iconPublisher: NetworkProtectionIconPublisher private var iconPublisherCancellable: AnyCancellable? @@ -70,10 +71,12 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { init(popoverManager: NetPPopoverManager, pinningManager: PinningManager = LocalPinningManager.shared, + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), statusReporter: NetworkProtectionStatusReporter, iconProvider: IconProvider = NavigationBarIconProvider()) { self.popoverManager = popoverManager + self.vpnVisibility = vpnVisibility self.networkProtectionStatusReporter = statusReporter self.iconPublisher = NetworkProtectionIconPublisher(statusReporter: networkProtectionStatusReporter, iconProvider: iconProvider) self.pinningManager = pinningManager @@ -175,8 +178,7 @@ final class NetworkProtectionNavBarButtonModel: NSObject, ObservableObject { @MainActor func updateVisibility() { // The button is visible in the case where NetP has not been activated, but the user has been invited and they haven't accepted T&Cs. - let networkProtectionVisibility = DefaultNetworkProtectionVisibility() - if networkProtectionVisibility.isNetworkProtectionBetaVisible() { + if vpnVisibility.isNetworkProtectionBetaVisible() { if NetworkProtectionWaitlist().readyToAcceptTermsAndConditions { showButton = true return diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 3cb7adc0a7..98de79c2a5 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -32,6 +32,7 @@ final class PreferencesSidebarModel: ObservableObject { @Published private(set) var sections: [PreferencesSection] = [] @Published var selectedTabIndex: Int = 0 @Published private(set) var selectedPane: PreferencePaneIdentifier = .defaultBrowser + private let vpnVisibility: NetworkProtectionFeatureVisibility var selectedTabContent: AnyPublisher { $selectedTabIndex.map { [tabSwitcherTabs] in tabSwitcherTabs[$0] }.eraseToAnyPublisher() @@ -43,10 +44,12 @@ final class PreferencesSidebarModel: ObservableObject { loadSections: @escaping () -> [PreferencesSection], tabSwitcherTabs: [Tab.TabContent], privacyConfigurationManager: PrivacyConfigurationManaging, - syncService: DDGSyncing + syncService: DDGSyncing, + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility() ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs + self.vpnVisibility = vpnVisibility resetTabSelectionIfNeeded() refreshSections() @@ -77,11 +80,12 @@ final class PreferencesSidebarModel: ObservableObject { tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, syncService: DDGSyncing, + vpnVisibility: NetworkProtectionFeatureVisibility = DefaultNetworkProtectionVisibility(), includeDuckPlayer: Bool, userDefaults: UserDefaults = .netP ) { let loadSections = { - let includingVPN = DefaultNetworkProtectionVisibility().isInstalled + let includingVPN = vpnVisibility.isInstalled return PreferencesSection.defaultSections( includingDuckPlayer: includeDuckPlayer, @@ -93,13 +97,14 @@ final class PreferencesSidebarModel: ObservableObject { self.init(loadSections: loadSections, tabSwitcherTabs: tabSwitcherTabs, privacyConfigurationManager: privacyConfigurationManager, - syncService: syncService) + syncService: syncService, + vpnVisibility: vpnVisibility) } // MARK: - Setup private func setupVPNPaneVisibility() { - DefaultNetworkProtectionVisibility().onboardStatusPublisher + vpnVisibility.onboardStatusPublisher .receive(on: DispatchQueue.main) .sink { [weak self] _ in guard let self else { return } diff --git a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift index 4a5d5113f3..dd06a09fb7 100644 --- a/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift +++ b/DuckDuckGo/PrivacyDashboard/View/PrivacyDashboardViewController.swift @@ -367,7 +367,7 @@ extension PrivacyDashboardViewController { errors: errors, httpStatusCodes: statusCodes, openerContext: currentTab.inferredOpenerContext, - vpnOn: currentTab.tunnelController?.isConnected ?? false, + vpnOn: currentTab.tunnelController.isConnected, jsPerformance: webVitals, userRefreshCount: currentTab.refreshCountSinceLoad, didOpenReportInfo: didOpenReportInfo, diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index 643da5aa94..bfa43c24e3 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -341,7 +341,7 @@ protocol NewWindowPolicyDecisionMaker { private let internalUserDecider: InternalUserDecider? let pinnedTabsManager: PinnedTabsManager - private(set) var tunnelController: NetworkProtectionIPCTunnelController? + private(set) var tunnelController: NetworkProtectionIPCTunnelController private let webViewConfiguration: WKWebViewConfiguration @@ -510,6 +510,10 @@ protocol NewWindowPolicyDecisionMaker { duckPlayer: duckPlayer, downloadManager: downloadManager)) + let ipcClient = TunnelControllerIPCClient() + ipcClient.register() + tunnelController = NetworkProtectionIPCTunnelController(ipcClient: ipcClient) + super.init() tabGetter = { [weak self] in self } userContentController.map(userContentControllerPromise.fulfill) @@ -529,17 +533,6 @@ protocol NewWindowPolicyDecisionMaker { self?.onDuckDuckGoEmailSignOut(notification) } - netPOnboardStatusCancellabel = DefaultNetworkProtectionVisibility().onboardStatusPublisher - .receive(on: DispatchQueue.main) - .sink { [weak self] onboardingStatus in - guard onboardingStatus == .completed else { return } - - let ipcClient = TunnelControllerIPCClient() - ipcClient.register() - - self?.tunnelController = NetworkProtectionIPCTunnelController(ipcClient: ipcClient) - } - self.audioState = webView.audioState() addDeallocationChecks(for: webView) } @@ -1170,8 +1163,6 @@ protocol NewWindowPolicyDecisionMaker { private var webViewCancellables = Set() private var emailDidSignOutCancellable: AnyCancellable? - private var netPOnboardStatusCancellabel: AnyCancellable? - private func setupWebView(shouldLoadInBackground: Bool) { webView.navigationDelegate = navigationDelegate webView.uiDelegate = self @@ -1456,7 +1447,7 @@ extension Tab/*: NavigationResponder*/ { // to be moved to Tab+Navigation.swift } } - if navigation.url.isDuckDuckGoSearch, tunnelController?.isConnected == true { + if navigation.url.isDuckDuckGoSearch, tunnelController.isConnected == true { DailyPixel.fire(pixel: .networkProtectionEnabledOnSearch, frequency: .dailyAndCount) } } diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift index 72d787249c..6ce1842ef4 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureVisibility.swift @@ -41,6 +41,8 @@ protocol NetworkProtectionFeatureVisibility { func disableForWaitlistUsers() @discardableResult func disableIfUserHasNoAccess() async -> Bool + + var onboardStatusPublisher: AnyPublisher { get } } struct DefaultNetworkProtectionVisibility: NetworkProtectionFeatureVisibility { diff --git a/UnitTests/Menus/MoreOptionsMenuTests.swift b/UnitTests/Menus/MoreOptionsMenuTests.swift index 1aec539d0c..f468cc4a28 100644 --- a/UnitTests/Menus/MoreOptionsMenuTests.swift +++ b/UnitTests/Menus/MoreOptionsMenuTests.swift @@ -16,14 +16,15 @@ // limitations under the License. // +import Combine +import NetworkProtection +import NetworkProtectionUI import XCTest #if SUBSCRIPTION import Subscription #endif -import NetworkProtection - @testable import DuckDuckGo_Privacy_Browser final class MoreOptionsMenuTests: XCTestCase { @@ -165,6 +166,9 @@ final class MoreOptionsMenuTests: XCTestCase { } final class NetworkProtectionVisibilityMock: NetworkProtectionFeatureVisibility { + var onboardStatusPublisher: AnyPublisher { + Just(.default).eraseToAnyPublisher() + } var isInstalled: Bool var visible: Bool From b9fa1148195733575912350cf3bd5e9b39cd22eb Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 9 Apr 2024 10:43:22 +0000 Subject: [PATCH 08/40] Bump version to 1.83.0 (154) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 06e5c06c5d..3f12085292 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 153 +CURRENT_PROJECT_VERSION = 154 From b5b07de499e3c3baa5aea99c512587e200c9354d Mon Sep 17 00:00:00 2001 From: Elle Sullivan Date: Tue, 9 Apr 2024 12:20:47 +0100 Subject: [PATCH 09/40] Add additonal error handling to DBP database and related classes (#2384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/0/1206814903024662/f Tech Design URL: CC: **Description**: Adds additional error handling mostly to the DB classes but also others. This requires updating quite a lot of classes to take this into account. Also makes the DBP tests have the same swift lint rules as the main unit tests Things this does not do: - Necessarily arrive at the final desired error handling for each class updated. Some errors are ignored with inert do/catches to allow the PR to be delivered without updating the entire codebase. - Add pixels (since they require a privacy triage) Also worth noting that sometimes this error handling introduces subtle behavior changes, where if a function uses a function that throws, it will now be rethrown, rather than the function plowing ahead regardless. I don't think there are any cases where this is an undesirable change, but worth calling out in case it turns out we were having non blocking errors that now suddenly become blocking. Long term I consider this to be a feature and part of the point. **Steps to test this PR**: 1. Obviously try the core functionality of DBP, but outside of that I don't think manual testing is the way to go with this 2. Now the other PRs are merged into this, there are some other things to test: - That error pixels are fired as expected. There are two general cases to to test: The secure vault error pixels, both from the background agent and from the main app The new "generalError" pixel (again both from the background agent and from the main app). Pay special attention to making sure it always has at least the following params: error code, error domain, and the origin function. Special care is needed here with the origin function, because if one uses pixel kit debug events it clobbers the original params - Simulate throwing various kinds of errors and make sure the processor receives them from the collection operations and fires the original pixel --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --------- Co-authored-by: Dominik Kapusta Co-authored-by: Fernando Bunn Co-authored-by: Dax the Duck Co-authored-by: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Co-authored-by: Diego Rey Mendez Co-authored-by: Pete Smith Co-authored-by: Sabrina Tardio <44158575+SabrinaTardio@users.noreply.github.com> Co-authored-by: Anh Do <18567+quanganhdo@users.noreply.github.com> Co-authored-by: Juan Manuel Pereira Co-authored-by: Christopher Brind Co-authored-by: Michal Smaga Co-authored-by: bwaresiak Co-authored-by: Mariusz Śpiewak Co-authored-by: Daniel Bernal Co-authored-by: muodov Co-authored-by: Sam Symons Co-authored-by: Alessandro Boron Co-authored-by: Federico Cappelli Co-authored-by: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Co-authored-by: Graeme Arthur Co-authored-by: amddg44 Co-authored-by: Brad Slayter Co-authored-by: Halle <378795+Halle@users.noreply.github.com> Co-authored-by: Diego Rey Mendez --- .../xcshareddata/swiftpm/Package.resolved | 2 +- DuckDuckGo/DBP/DBPHomeViewController.swift | 17 +- .../DBP/DataBrokerProtectionAppEvents.swift | 4 +- .../DBP/DataBrokerProtectionDebugMenu.swift | 36 ++- .../DataBrokerProtectionFeatureDisabler.swift | 6 +- ...taBrokerProtectionLoginItemScheduler.swift | 9 +- .../DBP/DataBrokerProtectionManager.swift | 2 +- ...ataBrokerProtectionBackgroundManager.swift | 41 ++- .../IPCServiceManager.swift | 27 +- .../CCF/DataBrokerProtectionErrors.swift | 3 + .../DataBrokerProtectionDataManager.swift | 88 +++--- .../DataBrokerProtectionDatabase.swift | 278 ++++++++++-------- ...erProtectionSecureVaultErrorReporter.swift | 44 +++ .../DataBrokerDatabaseBrowserViewModel.swift | 7 +- .../DataBrokerRunCustomJSONViewModel.swift | 2 +- .../DataBrokerForceOptOutViewModel.swift | 14 +- .../IPC/DataBrokerProtectionIPCClient.swift | 33 ++- .../DataBrokerProtectionIPCScheduler.swift | 9 +- .../IPC/DataBrokerProtectionIPCServer.swift | 27 +- .../Model/DBPUIViewModel.swift | 9 +- .../Model/DataBroker.swift | 20 +- .../DataBrokerOperationsCollection.swift | 35 ++- ...taBrokerProfileQueryOperationManager.swift | 42 +-- .../DataBrokerProtectionBrokerUpdater.swift | 34 ++- .../OperationPreferredDateUpdater.swift | 60 ++-- .../OperationRetriesCalculatorUseCase.swift | 8 +- .../MismatchCalculatorUseCase.swift | 9 +- .../Operations/ScanOperation.swift | 2 +- ...DataBrokerProtectionEngagementPixels.swift | 2 +- .../DataBrokerProtectionEventPixels.swift | 9 +- .../Pixels/DataBrokerProtectionPixels.swift | 21 +- ...kerProtectionStageDurationCalculator.swift | 3 + .../DataBrokerProtectionNoOpScheduler.swift | 6 +- .../DataBrokerProtectionProcessor.swift | 80 +++-- .../DataBrokerProtectionScheduler.swift | 104 ++++++- .../UI/DBPUICommunicationLayer.swift | 23 +- .../DataBrokerProtectionViewController.swift | 4 +- .../DataBrokerProtection/Tests/.swiftlint.yml | 17 ++ .../DataBrokerProtectionProfileTests.swift | 15 +- ...otectionStageDurationCalculatorTests.swift | 20 ++ .../DataBrokerProtectionTests/Mocks.swift | 4 +- .../OperationPreferredDateUpdaterTests.swift | 4 +- 42 files changed, 808 insertions(+), 372 deletions(-) create mode 100644 LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift create mode 100644 LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 46c657c96d..0e02888fb7 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -138,7 +138,7 @@ { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", + "location" : "https://github.com/apple/swift-syntax", "state" : { "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", "version" : "509.1.1" diff --git a/DuckDuckGo/DBP/DBPHomeViewController.swift b/DuckDuckGo/DBP/DBPHomeViewController.swift index fdfca10e97..c8d4c2b3ca 100644 --- a/DuckDuckGo/DBP/DBPHomeViewController.swift +++ b/DuckDuckGo/DBP/DBPHomeViewController.swift @@ -33,6 +33,7 @@ public extension Notification.Name { final class DBPHomeViewController: NSViewController { private var presentedWindowController: NSWindowController? private let dataBrokerProtectionManager: DataBrokerProtectionManager + private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() lazy var dataBrokerProtectionViewController: DataBrokerProtectionViewController = { let privacyConfigurationManager = PrivacyFeatures.contentBlocking.privacyConfigurationManager @@ -83,9 +84,14 @@ final class DBPHomeViewController: NSViewController { attachDataBrokerContainerView() } - if dataBrokerProtectionManager.dataManager.fetchProfile() != nil { - let dbpDateStore = DefaultWaitlistActivationDateStore(source: .dbp) - dbpDateStore.updateLastActiveDate() + do { + if try dataBrokerProtectionManager.dataManager.fetchProfile() != nil { + let dbpDateStore = DefaultWaitlistActivationDateStore(source: .dbp) + dbpDateStore.updateLastActiveDate() + } + } catch { + os_log("DBPHomeViewController error: viewDidLoad, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DBPHomeViewController.viewDidLoad")) } } @@ -146,6 +152,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping 0 { + if let profileQueries = try? DataBrokerProtectionManager.shared.dataManager.fetchBrokerProfileQueryData(ignoresCache: true), + profileQueries.count > 0 { restartBackgroundAgent(loginItemsManager: loginItemsManager) } else { featureVisibility.disableAndDeleteForWaitlistUsers() diff --git a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift index 44bbfad4b1..1478151aca 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionDebugMenu.swift @@ -204,9 +204,15 @@ final class DataBrokerProtectionDebugMenu: NSMenu { os_log("Running queued operations...", log: .dataBrokerProtection) let showWebView = sender.representedObject as? Bool ?? false - DataBrokerProtectionManager.shared.scheduler.runQueuedOperations(showWebView: showWebView) { error in - if let error = error { - os_log("Queued operations finished, error: %{public}@", log: .dataBrokerProtection, error.localizedDescription) + DataBrokerProtectionManager.shared.scheduler.runQueuedOperations(showWebView: showWebView) { errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Queued operations finished, error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Queued operations finished, operation errors count: %{public}@", log: .dataBrokerProtection, operationErrors.count) + } } else { os_log("Queued operations finished", log: .dataBrokerProtection) } @@ -217,9 +223,15 @@ final class DataBrokerProtectionDebugMenu: NSMenu { os_log("Running scan operations...", log: .dataBrokerProtection) let showWebView = sender.representedObject as? Bool ?? false - DataBrokerProtectionManager.shared.scheduler.scanAllBrokers(showWebView: showWebView) { error in - if let error = error { - os_log("Scan operations finished, error: %{public}@", log: .dataBrokerProtection, error.localizedDescription) + DataBrokerProtectionManager.shared.scheduler.scanAllBrokers(showWebView: showWebView) { errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("scan operations finished, error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("scan operations finished, operation errors count: %{public}@", log: .dataBrokerProtection, operationErrors.count) + } } else { os_log("Scan operations finished", log: .dataBrokerProtection) } @@ -230,9 +242,15 @@ final class DataBrokerProtectionDebugMenu: NSMenu { os_log("Running Optout operations...", log: .dataBrokerProtection) let showWebView = sender.representedObject as? Bool ?? false - DataBrokerProtectionManager.shared.scheduler.optOutAllBrokers(showWebView: showWebView) { error in - if let error = error { - os_log("Optout operations finished, error: %{public}@", log: .dataBrokerProtection, error.localizedDescription) + DataBrokerProtectionManager.shared.scheduler.optOutAllBrokers(showWebView: showWebView) { errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Optout operations finished, error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Optout operations finished, operation errors count: %{public}@", log: .dataBrokerProtection, operationErrors.count) + } } else { os_log("Optout operations finished", log: .dataBrokerProtection) } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift index b9ab9ac50a..d66ed38247 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionFeatureDisabler.swift @@ -46,7 +46,11 @@ struct DataBrokerProtectionFeatureDisabler: DataBrokerProtectionFeatureDisabling scheduler.disableLoginItem() - dataManager.removeAllData() + do { + try dataManager.removeAllData() + } catch { + os_log("DataBrokerProtectionFeatureDisabler error: disableAndDelete, error: %{public}@", log: .error, error.localizedDescription) + } DataBrokerProtectionLoginItemPixels.fire(pixel: .dataBrokerDisableAndDeleteDaily, frequency: .dailyOnly) NotificationCenter.default.post(name: .dbpWasDisabled, object: nil) diff --git a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift index feb254562a..34dafab63f 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionLoginItemScheduler.swift @@ -55,7 +55,8 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler ipcScheduler.statusPublisher } - func scanAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { + func scanAllBrokers(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { enableLoginItem() ipcScheduler.scanAllBrokers(showWebView: showWebView, completion: completion) } @@ -69,7 +70,8 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler ipcScheduler.stopScheduler() } - func optOutAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { + func optOutAllBrokers(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { ipcScheduler.optOutAllBrokers(showWebView: showWebView, completion: completion) } @@ -77,7 +79,8 @@ extension DataBrokerProtectionLoginItemScheduler: DataBrokerProtectionScheduler ipcScheduler.runAllOperations(showWebView: showWebView) } - func runQueuedOperations(showWebView: Bool, completion: ((Error?) -> Void)?) { + func runQueuedOperations(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { ipcScheduler.runQueuedOperations(showWebView: showWebView, completion: completion) } } diff --git a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift index 29ce721418..adb20e4aee 100644 --- a/DuckDuckGo/DBP/DataBrokerProtectionManager.swift +++ b/DuckDuckGo/DBP/DataBrokerProtectionManager.swift @@ -36,7 +36,7 @@ public final class DataBrokerProtectionManager { private let dataBrokerProtectionWaitlistDataSource: WaitlistActivationDateStore = DefaultWaitlistActivationDateStore(source: .dbp) lazy var dataManager: DataBrokerProtectionDataManager = { - let dataManager = DataBrokerProtectionDataManager(fakeBrokerFlag: fakeBrokerFlag) + let dataManager = DataBrokerProtectionDataManager(pixelHandler: pixelHandler, fakeBrokerFlag: fakeBrokerFlag) dataManager.delegate = self return dataManager }() diff --git a/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift b/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift index 787a859f90..2bdc06947a 100644 --- a/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift +++ b/DuckDuckGoDBPBackgroundAgent/DataBrokerProtectionBackgroundManager.swift @@ -36,7 +36,7 @@ public final class DataBrokerProtectionBackgroundManager { private lazy var ipcServiceManager = IPCServiceManager(scheduler: scheduler, pixelHandler: pixelHandler) lazy var dataManager: DataBrokerProtectionDataManager = { - DataBrokerProtectionDataManager(fakeBrokerFlag: fakeBrokerFlag) + DataBrokerProtectionDataManager(pixelHandler: pixelHandler, fakeBrokerFlag: fakeBrokerFlag) }() lazy var scheduler: DataBrokerProtectionScheduler = { @@ -78,19 +78,36 @@ public final class DataBrokerProtectionBackgroundManager { public func runOperationsAndStartSchedulerIfPossible() { pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossible) - // If there's no saved profile we don't need to start the scheduler - if dataManager.fetchProfile() != nil { - scheduler.runQueuedOperations(showWebView: false) { [weak self] error in - guard error == nil else { - // Ideally we'd fire a pixel here, however at the moment the scheduler never ever returns an error - return - } + do { + // If there's no saved profile we don't need to start the scheduler + guard (try dataManager.fetchProfile()) != nil else { + pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossibleNoSavedProfile) + return + } + } catch { + pixelHandler.fire(.generalError(error: error, + functionOccurredIn: "DataBrokerProtectionBackgroundManager.runOperationsAndStartSchedulerIfPossible")) + return + } - self?.pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossibleRunQueuedOperationsCallbackStartScheduler) - self?.scheduler.startScheduler() + scheduler.runQueuedOperations(showWebView: false) { [weak self] errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Error during BackgroundManager runOperationsAndStartSchedulerIfPossible in scheduler.runQueuedOperations(), error: %{public}@", + log: .dataBrokerProtection, + oneTimeError.localizedDescription) + self?.pixelHandler.fire(.generalError(error: oneTimeError, + functionOccurredIn: "DataBrokerProtectionBackgroundManager.runOperationsAndStartSchedulerIfPossible")) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Operation error(s) during BackgroundManager runOperationsAndStartSchedulerIfPossible in scheduler.runQueuedOperations(), count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + } + return } - } else { - pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossibleNoSavedProfile) + + self?.pixelHandler.fire(.backgroundAgentRunOperationsAndStartSchedulerIfPossibleRunQueuedOperationsCallbackStartScheduler) + self?.scheduler.startScheduler() } } } diff --git a/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift b/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift index c452200f7b..3f9361e6e5 100644 --- a/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift +++ b/DuckDuckGoDBPBackgroundAgent/IPCServiceManager.swift @@ -77,27 +77,30 @@ extension IPCServiceManager: IPCServerInterface { scheduler.stopScheduler() } - func optOutAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + func optOutAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { pixelHandler.fire(.ipcServerOptOutAllBrokers) - scheduler.optOutAllBrokers(showWebView: showWebView) { error in - self.pixelHandler.fire(.ipcServerOptOutAllBrokersCompletion(error: error)) - completion(error) + scheduler.optOutAllBrokers(showWebView: showWebView) { errors in + self.pixelHandler.fire(.ipcServerOptOutAllBrokersCompletion(error: errors?.oneTimeError)) + completion(errors) } } - func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + func scanAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { pixelHandler.fire(.ipcServerScanAllBrokers) - scheduler.scanAllBrokers(showWebView: showWebView) { error in - self.pixelHandler.fire(.ipcServerScanAllBrokersCompletion(error: error)) - completion(error) + scheduler.scanAllBrokers(showWebView: showWebView) { errors in + self.pixelHandler.fire(.ipcServerScanAllBrokersCompletion(error: errors?.oneTimeError)) + completion(errors) } } - func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + func runQueuedOperations(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { pixelHandler.fire(.ipcServerRunQueuedOperations) - scheduler.runQueuedOperations(showWebView: showWebView) { error in - self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: error)) - completion(error) + scheduler.runQueuedOperations(showWebView: showWebView) { errors in + self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: errors?.oneTimeError)) + completion(errors) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift index 63ebe88d77..d48373aeb0 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionErrors.swift @@ -38,6 +38,7 @@ public enum DataBrokerProtectionError: Error, Equatable, Codable { case solvingCaptchaWithCallbackError case cantCalculatePreferredRunDate case httpError(code: Int) + case dataNotInDatabase static func parse(params: Any) -> DataBrokerProtectionError { let errorDataResult = try? JSONSerialization.data(withJSONObject: params) @@ -88,6 +89,8 @@ extension DataBrokerProtectionError { return "cantCalculatePreferredRunDate" case .httpError: return "httpError" + case .dataNotInDatabase: + return "dataNotInDatabase" } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift index 50f5bb47fe..6ff680717b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDataManager.swift @@ -23,20 +23,18 @@ public protocol DataBrokerProtectionDataManaging { var cache: InMemoryDataCache { get } var delegate: DataBrokerProtectionDataManagerDelegate? { get set } - init(fakeBrokerFlag: DataBrokerDebugFlag) - func saveProfile(_ profile: DataBrokerProtectionProfile) async -> Bool - func fetchProfile(ignoresCache: Bool) -> DataBrokerProtectionProfile? - func fetchBrokerProfileQueryData(ignoresCache: Bool) async -> [BrokerProfileQueryData] - func hasMatches() -> Bool + init(pixelHandler: EventMapping, fakeBrokerFlag: DataBrokerDebugFlag) + func saveProfile(_ profile: DataBrokerProtectionProfile) async throws + func fetchProfile() throws -> DataBrokerProtectionProfile? + func prepareProfileCache() throws + func fetchBrokerProfileQueryData(ignoresCache: Bool) throws -> [BrokerProfileQueryData] + func prepareBrokerProfileQueryDataCache() throws + func hasMatches() throws -> Bool } extension DataBrokerProtectionDataManaging { - func fetchProfile() -> DataBrokerProtectionProfile? { - fetchProfile(ignoresCache: false) - } - - func fetchBrokerProfileQueryData() async -> [BrokerProfileQueryData] { - await fetchBrokerProfileQueryData(ignoresCache: false) + func fetchBrokerProfileQueryData() throws -> [BrokerProfileQueryData] { + try fetchBrokerProfileQueryData(ignoresCache: false) } } @@ -52,27 +50,31 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { internal let database: DataBrokerProtectionRepository - required public init(fakeBrokerFlag: DataBrokerDebugFlag = DataBrokerDebugFlagFakeBroker()) { - self.database = DataBrokerProtectionDatabase(fakeBrokerFlag: fakeBrokerFlag) + required public init(pixelHandler: EventMapping, fakeBrokerFlag: DataBrokerDebugFlag = DataBrokerDebugFlagFakeBroker()) { + self.database = DataBrokerProtectionDatabase(fakeBrokerFlag: fakeBrokerFlag, pixelHandler: pixelHandler) cache.delegate = self } - public func saveProfile(_ profile: DataBrokerProtectionProfile) async -> Bool { - let result = await database.save(profile) + public func saveProfile(_ profile: DataBrokerProtectionProfile) async throws { + do { + try await database.save(profile) + } catch { + // We should still invalidate the cache if the save fails + cache.invalidate() + throw error + } cache.invalidate() cache.profile = profile - - return result } - public func fetchProfile(ignoresCache: Bool = false) -> DataBrokerProtectionProfile? { - if !ignoresCache, cache.profile != nil { + public func fetchProfile() throws -> DataBrokerProtectionProfile? { + if cache.profile != nil { os_log("Returning cached profile", log: .dataBrokerProtection) return cache.profile } - if let profile = database.fetchProfile() { + if let profile = try database.fetchProfile() { cache.profile = profile return profile } else { @@ -81,34 +83,43 @@ public class DataBrokerProtectionDataManager: DataBrokerProtectionDataManaging { } } - public func fetchBrokerProfileQueryData(ignoresCache: Bool = false) async -> [BrokerProfileQueryData] { + public func prepareProfileCache() throws { + if let profile = try database.fetchProfile() { + cache.profile = profile + } else { + os_log("No profile found", log: .dataBrokerProtection) + } + } + + public func fetchBrokerProfileQueryData(ignoresCache: Bool = false) throws -> [BrokerProfileQueryData] { if !ignoresCache, !cache.brokerProfileQueryData.isEmpty { os_log("Returning cached brokerProfileQueryData", log: .dataBrokerProtection) return cache.brokerProfileQueryData } - let queryData = database.fetchAllBrokerProfileQueryData() + let queryData = try database.fetchAllBrokerProfileQueryData() cache.brokerProfileQueryData = queryData return queryData } - public func hasMatches() -> Bool { - return database.hasMatches() + public func prepareBrokerProfileQueryDataCache() throws { + cache.brokerProfileQueryData = try database.fetchAllBrokerProfileQueryData() + } + + public func hasMatches() throws -> Bool { + return try database.hasMatches() } } extension DataBrokerProtectionDataManager: InMemoryDataCacheDelegate { - public func flushCache(profile: DataBrokerProtectionProfile?) async -> Bool { - guard let profile = profile else { return false } - let result = await saveProfile(profile) + public func saveCachedProfileToDatabase(_ profile: DataBrokerProtectionProfile) async throws { + try await saveProfile(profile) delegate?.dataBrokerProtectionDataManagerDidUpdateData() - - return result } - public func removeAllData() { - database.deleteProfileData() + public func removeAllData() throws { + try database.deleteProfileData() cache.invalidate() delegate?.dataBrokerProtectionDataManagerDidDeleteData() @@ -116,8 +127,8 @@ extension DataBrokerProtectionDataManager: InMemoryDataCacheDelegate { } public protocol InMemoryDataCacheDelegate: AnyObject { - func flushCache(profile: DataBrokerProtectionProfile?) async -> Bool - func removeAllData() + func saveCachedProfileToDatabase(_ profile: DataBrokerProtectionProfile) async throws + func removeAllData() throws } public final class InMemoryDataCache { @@ -139,10 +150,9 @@ public final class InMemoryDataCache { } extension InMemoryDataCache: DBPUICommunicationDelegate { - func saveProfile() async -> Bool { - _ = await delegate?.flushCache(profile: profile) - - return true + func saveProfile() async throws { + guard let profile = profile else { return } + try await delegate?.saveCachedProfileToDatabase(profile) } private func indexForName(matching name: DBPUIUserProfileName, in profile: DataBrokerProtectionProfile) -> Int? { @@ -178,9 +188,9 @@ extension InMemoryDataCache: DBPUICommunicationDelegate { return DBPUIUserProfile(names: names, birthYear: profile.birthYear, addresses: addresses) } - func deleteProfileData() { + func deleteProfileData() throws { profile = emptyProfile - delegate?.removeAllData() + try delegate?.removeAllData() } func addNameToCurrentUserProfile(_ name: DBPUIUserProfileName) -> Bool { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift index 794b2422fc..8dbfe0b9cb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionDatabase.swift @@ -18,176 +18,195 @@ import Foundation import Common +import SecureStorage protocol DataBrokerProtectionRepository { - func save(_ profile: DataBrokerProtectionProfile) async -> Bool - func fetchProfile() -> DataBrokerProtectionProfile? - func deleteProfileData() + func save(_ profile: DataBrokerProtectionProfile) async throws + func fetchProfile() throws -> DataBrokerProtectionProfile? + func deleteProfileData() throws - func fetchChildBrokers(for parentBroker: String) -> [DataBroker] + func fetchChildBrokers(for parentBroker: String) throws -> [DataBroker] func saveOptOutOperation(optOut: OptOutOperationData, extractedProfile: ExtractedProfile) throws - func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) -> BrokerProfileQueryData? - func fetchAllBrokerProfileQueryData() -> [BrokerProfileQueryData] - func fetchExtractedProfiles(for brokerId: Int64) -> [ExtractedProfile] + func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) throws -> BrokerProfileQueryData? + func fetchAllBrokerProfileQueryData() throws -> [BrokerProfileQueryData] + func fetchExtractedProfiles(for brokerId: Int64) throws -> [ExtractedProfile] - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) - func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws + func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) throws - func add(_ historyEvent: HistoryEvent) - func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) -> HistoryEvent? - func fetchScanHistoryEvents(brokerId: Int64, profileQueryId: Int64) -> [HistoryEvent] - func fetchOptOutHistoryEvents(brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) -> [HistoryEvent] - func hasMatches() -> Bool + func add(_ historyEvent: HistoryEvent) throws + func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) throws -> HistoryEvent? + func fetchScanHistoryEvents(brokerId: Int64, profileQueryId: Int64) throws -> [HistoryEvent] + func fetchOptOutHistoryEvents(brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws -> [HistoryEvent] + func hasMatches() throws -> Bool - func fetchAttemptInformation(for extractedProfileId: Int64) -> AttemptInformation? - func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) + func fetchAttemptInformation(for extractedProfileId: Int64) throws -> AttemptInformation? + func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) throws } final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { private static let profileId: Int64 = 1 // At the moment, we only support one profile for DBP. private let fakeBrokerFlag: DataBrokerDebugFlag + private let pixelHandler: EventMapping private let vault: (any DataBrokerProtectionSecureVault)? + private let secureVaultErrorReporter: SecureVaultErrorReporting? - init(fakeBrokerFlag: DataBrokerDebugFlag = DataBrokerDebugFlagFakeBroker(), vault: (any DataBrokerProtectionSecureVault)? = nil) { + init(fakeBrokerFlag: DataBrokerDebugFlag = DataBrokerDebugFlagFakeBroker(), + pixelHandler: EventMapping, + vault: (any DataBrokerProtectionSecureVault)? = nil, + secureVaultErrorReporter: SecureVaultErrorReporting? = DataBrokerProtectionSecureVaultErrorReporter.shared) { self.fakeBrokerFlag = fakeBrokerFlag + self.pixelHandler = pixelHandler self.vault = vault + self.secureVaultErrorReporter = secureVaultErrorReporter } - func save(_ profile: DataBrokerProtectionProfile) async -> Bool { + func save(_ profile: DataBrokerProtectionProfile) async throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) if try vault.fetchProfile(with: Self.profileId) != nil { try await updateProfile(profile, vault: vault) } else { try await saveNewProfile(profile, vault: vault) } - - return true } catch { - os_log("Database error: saveProfile, error: %{public}@", log: .error, error.localizedDescription) - - return false + os_log("Database error: save profile, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.save profile")) + throw error } } - public func fetchProfile() -> DataBrokerProtectionProfile? { + public func fetchProfile() throws -> DataBrokerProtectionProfile? { do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchProfile(with: Self.profileId) } catch { os_log("Database error: fetchProfile, error: %{public}@", log: .error, error.localizedDescription) - return nil + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchProfile")) + throw error } } - public func deleteProfileData() { + public func deleteProfileData() throws { do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.deleteProfileData() } catch { - os_log("Database error: removeProfileData, error: %{public}@", log: .error, error.localizedDescription) - return + os_log("Database error: deleteProfileData, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.deleteProfileData")) + throw error } } - func fetchChildBrokers(for parentBroker: String) -> [DataBroker] { + func fetchChildBrokers(for parentBroker: String) throws -> [DataBroker] { do { - let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchChildBrokers(for: parentBroker) } catch { os_log("Database error: fetchChildBrokers, error: %{public}@", log: .error, error.localizedDescription) - return [DataBroker]() + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchChildBrokers for parentBroker")) + throw error } } func save(_ extractedProfile: ExtractedProfile, brokerId: Int64, profileQueryId: Int64) throws -> Int64 { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) - return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) + return try vault.save(extractedProfile: extractedProfile, brokerId: brokerId, profileQueryId: profileQueryId) + } catch { + os_log("Database error: extractedProfile, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.save extractedProfile brokerId profileQueryId")) + throw error + } } - func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) -> BrokerProfileQueryData? { + func brokerProfileQueryData(for brokerId: Int64, and profileQueryId: Int64) throws -> BrokerProfileQueryData? { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - if let broker = try vault.fetchBroker(with: brokerId), - let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), - let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) { - - let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) - - return BrokerProfileQueryData( - dataBroker: broker, - profileQuery: profileQuery, - scanOperationData: scanOperation, - optOutOperationsData: optOutOperations - ) - } else { - // We should throw here. The caller probably needs to know that some of the - // models he was looking for where not found. This will be worked on the error handling task/project. - return nil + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) + guard let broker = try vault.fetchBroker(with: brokerId), + let profileQuery = try vault.fetchProfileQuery(with: profileQueryId), + let scanOperation = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { + let error = DataBrokerProtectionError.dataNotInDatabase + os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.brokerProfileQueryData for brokerId and profileQueryId")) + throw error } + + let optOutOperations = try vault.fetchOptOuts(brokerId: brokerId, profileQueryId: profileQueryId) + + return BrokerProfileQueryData( + dataBroker: broker, + profileQuery: profileQuery, + scanOperationData: scanOperation, + optOutOperationsData: optOutOperations + ) } catch { os_log("Database error: brokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) - return nil + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.brokerProfileQueryData for brokerId and profileQueryId")) + throw error } } - func fetchExtractedProfiles(for brokerId: Int64) -> [ExtractedProfile] { + func fetchExtractedProfiles(for brokerId: Int64) throws -> [ExtractedProfile] { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchExtractedProfiles(for: brokerId) } catch { - os_log("Database error: fetchExtractedProfiles for scan, error: %{public}@", log: .error, error.localizedDescription) - return [ExtractedProfile]() + os_log("Database error: fetchExtractedProfiles, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchExtractedProfiles for brokerId")) + throw error } } - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) { + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } catch { - os_log("Database error: updatePreferredRunDate for scan, error: %{public}@", log: .error, error.localizedDescription) + os_log("Database error: updatePreferredRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updatePreferredRunDate date brokerID profileQueryId")) + throw error } } - func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) { + func updatePreferredRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updatePreferredRunDate( date, brokerId: brokerId, profileQueryId: profileQueryId, - extractedProfileId: extractedProfileId - ) + extractedProfileId: extractedProfileId) } catch { - os_log("Database error: updatePreferredRunDate for optOut, error: %{public}@", log: .error, error.localizedDescription) + os_log("Database error: updatePreferredRunDate with extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updatePreferredRunDate date brokerID profileQueryId extractedProfileID")) + throw error } } - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) { + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updateLastRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) } catch { - os_log("Database error: updateLastRunDate for scan, error: %{public}@", log: .error, error.localizedDescription) + os_log("Database error: updateLastRunDate without extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateLastRunDate date brokerID profileQueryId")) + throw error } } - func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) { + func updateLastRunDate(_ date: Date?, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updateLastRunDate( date, @@ -196,23 +215,26 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { extractedProfileId: extractedProfileId ) } catch { - os_log("Database error: updateLastRunDate for optOut, error: %{public}@", log: .error, error.localizedDescription) + os_log("Database error: updateLastRunDate with extractedProfileID, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateLastRunDate date brokerId profileQueryId extractedProfileId")) + throw error } } - func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) { + func updateRemovedDate(_ date: Date?, on extractedProfileId: Int64) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.updateRemovedDate(for: extractedProfileId, with: date) } catch { - os_log("Database error: updateRemoveDate, error: %{public}@", log: .error, error.localizedDescription) + os_log("Database error: updateRemovedDate, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.updateRemovedDate date on extractedProfileId")) + throw error } } - func add(_ historyEvent: HistoryEvent) { + func add(_ historyEvent: HistoryEvent) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) if let extractedProfileId = historyEvent.extractedProfileId { try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId, extractedProfileId: extractedProfileId) @@ -220,13 +242,15 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { try vault.save(historyEvent: historyEvent, brokerId: historyEvent.brokerId, profileQueryId: historyEvent.profileQueryId) } } catch { - os_log("Database error: addHistoryEvent, error: %{public}@", log: .error, error.localizedDescription) + os_log("Database error: add historyEvent, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.add historyEvent")) + throw error } } - func fetchAllBrokerProfileQueryData() -> [BrokerProfileQueryData] { + func fetchAllBrokerProfileQueryData() throws -> [BrokerProfileQueryData] { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) let brokers = try vault.fetchAllBrokers() let profileQueries = try vault.fetchAllProfileQueries(for: Self.profileId) var brokerProfileQueryDataList = [BrokerProfileQueryData]() @@ -252,33 +276,52 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return brokerProfileQueryDataList } catch { os_log("Database error: fetchAllBrokerProfileQueryData, error: %{public}@", log: .error, error.localizedDescription) - return [BrokerProfileQueryData]() + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchAllBrokerProfileQueryData")) + throw error } } func saveOptOutOperation(optOut: OptOutOperationData, extractedProfile: ExtractedProfile) throws { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) - try vault.save(brokerId: optOut.brokerId, - profileQueryId: optOut.profileQueryId, - extractedProfile: extractedProfile, - lastRunDate: optOut.lastRunDate, - preferredRunDate: optOut.preferredRunDate) + try vault.save(brokerId: optOut.brokerId, + profileQueryId: optOut.profileQueryId, + extractedProfile: extractedProfile, + lastRunDate: optOut.lastRunDate, + preferredRunDate: optOut.preferredRunDate) + } catch { + os_log("Database error: saveOptOutOperation, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.saveOptOutOperation optOut extractedProfile")) + throw error + } } - func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) -> HistoryEvent? { + func fetchLastEvent(brokerId: Int64, profileQueryId: Int64) throws -> HistoryEvent? { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) let events = try vault.fetchEvents(brokerId: brokerId, profileQueryId: profileQueryId) return events.max(by: { $0.date < $1.date }) } catch { os_log("Database error: fetchLastEvent, error: %{public}@", log: .error, error.localizedDescription) - return nil + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "fetchLastEvent brokerId profileQueryId")) + throw error + } + } + + func hasMatches() throws -> Bool { + do { + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) + return try vault.hasMatches() + } catch { + os_log("Database error: hasMatches, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.hasMatches")) + throw error } } - func fetchScanHistoryEvents(brokerId: Int64, profileQueryId: Int64) -> [HistoryEvent] { + func fetchScanHistoryEvents(brokerId: Int64, profileQueryId: Int64) throws -> [HistoryEvent] { do { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) guard let scan = try vault.fetchScan(brokerId: brokerId, profileQueryId: profileQueryId) else { @@ -288,11 +331,12 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return scan.historyEvents } catch { os_log("Database error: fetchHistoryEvents, error: %{public}@", log: .error, error.localizedDescription) - return [HistoryEvent]() + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "fetchScanHistoryEvents brokerId profileQueryId")) + throw error } } - func fetchOptOutHistoryEvents(brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) -> [HistoryEvent] { + func fetchOptOutHistoryEvents(brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws -> [HistoryEvent] { do { let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) guard let optOut = try vault.fetchOptOut(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) else { @@ -302,33 +346,25 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { return optOut.historyEvents } catch { os_log("Database error: fetchHistoryEvents, error: %{public}@", log: .error, error.localizedDescription) - return [HistoryEvent]() + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchOptOutHistoryEvents brokerId profileQueryId extractedProfileId")) + throw error } } - func hasMatches() -> Bool { + func fetchAttemptInformation(for extractedProfileId: Int64) throws -> AttemptInformation? { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) - return try vault.hasMatches() - } catch { - os_log("Database error: wereThereAnyMatches, error: %{public}@", log: .error, error.localizedDescription) - return false - } - } - - func fetchAttemptInformation(for extractedProfileId: Int64) -> AttemptInformation? { - do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) return try vault.fetchAttemptInformation(for: extractedProfileId) } catch { os_log("Database error: fetchAttemptInformation, error: %{public}@", log: .error, error.localizedDescription) - return nil + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.fetchAttemptInformation for extractedProfileId")) + throw error } } - func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) { + func addAttempt(extractedProfileId: Int64, attemptUUID: UUID, dataBroker: String, lastStageDate: Date, startTime: Date) throws { do { - let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) + let vault = try self.vault ?? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: secureVaultErrorReporter) try vault.save(extractedProfileId: extractedProfileId, attemptUUID: attemptUUID, dataBroker: dataBroker, @@ -336,6 +372,8 @@ final class DataBrokerProtectionDatabase: DataBrokerProtectionRepository { startTime: startTime) } catch { os_log("Database error: addAttempt, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionDatabase.addAttempt extractedProfileId attemptUUID dataBroker lastStageDate startTime")) + throw error } } } @@ -365,7 +403,7 @@ extension DataBrokerProtectionDatabase { let newProfileQueries = profile.profileQueries _ = try vault.save(profile: profile) - if let brokers = FileResources().fetchBrokerFromResourceFiles() { + if let brokers = try FileResources().fetchBrokerFromResourceFiles() { var brokerIDs = [Int64]() for broker in brokers { @@ -387,7 +425,7 @@ extension DataBrokerProtectionDatabase { let newProfileQueries = profile.profileQueries - let databaseBrokerProfileQueryData = fetchAllBrokerProfileQueryData() + let databaseBrokerProfileQueryData = try fetchAllBrokerProfileQueryData() let databaseProfileQueries = databaseBrokerProfileQueryData.map { $0.profileQuery } // The queries we need to create are the one that exist on the new ones but not in the database @@ -462,7 +500,7 @@ extension DataBrokerProtectionDatabase { if !profile.deprecated { for brokerID in brokerIDs where !profile.deprecated { - updatePreferredRunDate(Date(), brokerId: brokerID, profileQueryId: profileQueryID) + try updatePreferredRunDate(Date(), brokerId: brokerID, profileQueryId: profileQueryID) } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift new file mode 100644 index 0000000000..21cb003862 --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Database/DataBrokerProtectionSecureVaultErrorReporter.swift @@ -0,0 +1,44 @@ +// +// DataBrokerProtectionSecureVaultErrorReporter.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import BrowserServicesKit +import SecureStorage +import PixelKit +import Common + +final class DataBrokerProtectionSecureVaultErrorReporter: SecureVaultErrorReporting { + static let shared = DataBrokerProtectionSecureVaultErrorReporter(pixelHandler: DataBrokerProtectionPixelsHandler()) + + let pixelHandler: EventMapping + private init(pixelHandler: EventMapping) { + self.pixelHandler = pixelHandler + } + + func secureVaultInitFailed(_ error: SecureStorageError) { + switch error { + case .initFailed, .failedToOpenDatabase: + pixelHandler.fire(.secureVaultInitError(error: error)) + default: + pixelHandler.fire(.secureVaultError(error: error)) + } + + // Also fire the general pixel, since for now all errors are kept under one pixel + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "secureVaultErrorReporter")) + } +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift index 514957192a..a3bf0d1547 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBaseBrowser/DataBrokerDatabaseBrowserViewModel.swift @@ -31,7 +31,7 @@ final class DataBrokerDatabaseBrowserViewModel: ObservableObject { self.selectedTable = tables.first self.dataManager = nil } else { - self.dataManager = DataBrokerProtectionDataManager() + self.dataManager = DataBrokerProtectionDataManager(pixelHandler: DataBrokerProtectionPixelsHandler()) self.tables = [DataBrokerDatabaseBrowserData.Table]() self.selectedTable = nil updateTables() @@ -48,7 +48,10 @@ final class DataBrokerDatabaseBrowserViewModel: ObservableObject { guard let dataManager = self.dataManager else { return } Task { - let data = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + guard let data = try? dataManager.fetchBrokerProfileQueryData(ignoresCache: true) else { + assertionFailure("DataManager error during DataBrokerDatavaseBrowserViewModel.updateTables") + return + } let profileBrokers = data.map { $0.dataBroker } let dataBrokers = Array(Set(profileBrokers)).sorted { $0.id ?? 0 < $1.id ?? 0 } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift index 3e0d151dc9..e7b72fb99b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/DataBrokerRunCustomJSONViewModel.swift @@ -174,7 +174,7 @@ final class DataBrokerRunCustomJSONViewModel: ObservableObject { self.contentScopeProperties = contentScopeProperties let fileResources = FileResources() - self.brokers = fileResources.fetchBrokerFromResourceFiles() ?? [DataBroker]() + self.brokers = (try? fileResources.fetchBrokerFromResourceFiles()) ?? [DataBroker]() } func runAllBrokers() { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift index 3668052d2f..322af7070d 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/DebugUI/ForceOptOut/DataBrokerForceOptOutViewModel.swift @@ -23,14 +23,18 @@ final class DataBrokerForceOptOutViewModel: ObservableObject { private let dataManager: DataBrokerProtectionDataManager @Published var optOutData = [OptOutViewData]() - internal init(dataManager: DataBrokerProtectionDataManager = DataBrokerProtectionDataManager()) { + internal init(dataManager: DataBrokerProtectionDataManager = + DataBrokerProtectionDataManager(pixelHandler: DataBrokerProtectionPixelsHandler())) { self.dataManager = dataManager loadNotRemovedOptOutData() } private func loadNotRemovedOptOutData() { Task { @MainActor in - let brokerProfileData = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + guard let brokerProfileData = try? dataManager.fetchBrokerProfileQueryData(ignoresCache: true) else { + assertionFailure() + return + } self.optOutData = brokerProfileData .flatMap { profileData in profileData.optOutOperationsData.map { ($0, profileData.dataBroker.name) } @@ -70,6 +74,10 @@ struct OptOutViewData: Identifiable { private extension DataBrokerProtectionDataManager { func setAsRemoved(_ extractedProfileID: Int64) { - self.database.updateRemovedDate(Date(), on: extractedProfileID) + do { + try self.database.updateRemovedDate(Date(), on: extractedProfileID) + } catch { + assertionFailure() + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift index 2aa3953437..f651a62440 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCClient.swift @@ -102,42 +102,45 @@ extension DataBrokerProtectionIPCClient: IPCServerInterface { }) } - public func optOutAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + public func optOutAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { self.pixelHandler.fire(.ipcServerOptOutAllBrokers) xpc.execute(call: { server in - server.optOutAllBrokers(showWebView: showWebView) { error in - self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: error)) - completion(error) + server.optOutAllBrokers(showWebView: showWebView) { errors in + self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: errors?.oneTimeError)) + completion(errors) } }, xpcReplyErrorHandler: { error in self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: error)) - completion(error) + completion(DataBrokerProtectionSchedulerErrorCollection(oneTimeError: error)) }) } - public func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + public func scanAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { self.pixelHandler.fire(.ipcServerScanAllBrokers) xpc.execute(call: { server in - server.scanAllBrokers(showWebView: showWebView) { error in - self.pixelHandler.fire(.ipcServerScanAllBrokersCompletion(error: error)) - completion(error) + server.scanAllBrokers(showWebView: showWebView) { errors in + self.pixelHandler.fire(.ipcServerScanAllBrokersCompletion(error: errors?.oneTimeError)) + completion(errors) } }, xpcReplyErrorHandler: { error in self.pixelHandler.fire(.ipcServerScanAllBrokersCompletion(error: error)) - completion(error) + completion(DataBrokerProtectionSchedulerErrorCollection(oneTimeError: error)) }) } - public func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + public func runQueuedOperations(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { self.pixelHandler.fire(.ipcServerRunQueuedOperations) xpc.execute(call: { server in - server.runQueuedOperations(showWebView: showWebView) { error in - self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: error)) - completion(error) + server.runQueuedOperations(showWebView: showWebView) { errors in + self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: errors?.oneTimeError)) + completion(errors) } }, xpcReplyErrorHandler: { error in self.pixelHandler.fire(.ipcServerRunQueuedOperationsCompletion(error: error)) - completion(error) + completion(DataBrokerProtectionSchedulerErrorCollection(oneTimeError: error)) }) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCScheduler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCScheduler.swift index b69402626d..6f9bb8aa9f 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCScheduler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCScheduler.swift @@ -46,17 +46,20 @@ public final class DataBrokerProtectionIPCScheduler: DataBrokerProtectionSchedul ipcClient.stopScheduler() } - public func optOutAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { + public func optOutAllBrokers(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { let completion = completion ?? { _ in } ipcClient.optOutAllBrokers(showWebView: showWebView, completion: completion) } - public func scanAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { + public func scanAllBrokers(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { let completion = completion ?? { _ in } ipcClient.scanAllBrokers(showWebView: showWebView, completion: completion) } - public func runQueuedOperations(showWebView: Bool, completion: ((Error?) -> Void)?) { + public func runQueuedOperations(showWebView: Bool, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { let completion = completion ?? { _ in } ipcClient.runQueuedOperations(showWebView: showWebView, completion: completion) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift index a2bc3d0e56..33594703cb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/IPC/DataBrokerProtectionIPCServer.swift @@ -38,9 +38,12 @@ public protocol IPCServerInterface: AnyObject { /// func stopScheduler() - func optOutAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) - func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) - func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void)) + func optOutAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) + func scanAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) + func runQueuedOperations(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) func runAllOperations(showWebView: Bool) // MARK: - Debugging Features @@ -73,9 +76,12 @@ protocol XPCServerInterface { /// func stopScheduler() - func optOutAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) - func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) - func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void)) + func optOutAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) + func scanAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) + func runQueuedOperations(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) func runAllOperations(showWebView: Bool) // MARK: - Debugging Features @@ -143,15 +149,18 @@ extension DataBrokerProtectionIPCServer: XPCServerInterface { serverDelegate?.stopScheduler() } - func optOutAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + func optOutAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { serverDelegate?.optOutAllBrokers(showWebView: showWebView, completion: completion) } - func scanAllBrokers(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + func scanAllBrokers(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { serverDelegate?.scanAllBrokers(showWebView: showWebView, completion: completion) } - func runQueuedOperations(showWebView: Bool, completion: @escaping ((Error?) -> Void)) { + func runQueuedOperations(showWebView: Bool, + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { serverDelegate?.runQueuedOperations(showWebView: showWebView, completion: completion) } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift index ef1db86240..570a8f1e60 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DBPUIViewModel.swift @@ -20,6 +20,7 @@ import Foundation import Combine import WebKit import BrowserServicesKit +import Common protocol DBPUIScanOps: AnyObject { func startScan() -> Bool @@ -35,6 +36,7 @@ final class DBPUIViewModel { private var communicationLayer: DBPUICommunicationLayer? private var webView: WKWebView? private let webUISettings: DataBrokerProtectionWebUIURLSettingsRepresentable + private let pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler() init(dataManager: DataBrokerProtectionDataManaging, scheduler: DataBrokerProtectionScheduler, @@ -77,6 +79,11 @@ extension DBPUIViewModel: DBPUIScanOps { } func updateCacheWithCurrentScans() async { - _ = await dataManager.fetchBrokerProfileQueryData(ignoresCache: true) + do { + try dataManager.prepareBrokerProfileQueryDataCache() + } catch { + os_log("DBPUIViewModel error: updateCacheWithCurrentScans, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DBPUIViewModel.updateCacheWithCurrentScans")) + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift index 5408f88ea7..41a5293846 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Model/DataBroker.swift @@ -17,6 +17,7 @@ // import Foundation +import Common struct DataBrokerScheduleConfig: Codable { let retryError: Int @@ -172,14 +173,17 @@ struct DataBroker: Codable, Sendable { return optOutType == .parentSiteOptOut } - static func initFromResource(_ url: URL) -> DataBroker { - // swiftlint:disable:next force_try - let data = try! Data(contentsOf: url) - let jsonDecoder = JSONDecoder() - jsonDecoder.dateDecodingStrategy = .millisecondsSince1970 - // swiftlint:disable:next force_try - let broker = try! jsonDecoder.decode(DataBroker.self, from: data) - return broker + static func initFromResource(_ url: URL) throws -> DataBroker { + do { + let data = try Data(contentsOf: url) + let jsonDecoder = JSONDecoder() + jsonDecoder.dateDecodingStrategy = .millisecondsSince1970 + let broker = try jsonDecoder.decode(DataBroker.self, from: data) + return broker + } catch { + os_log("DataBroker error: initFromResource, error: %{public}@", log: .error, error.localizedDescription) + throw error + } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift index d77c7c7d6b..78b76af560 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerOperationsCollection.swift @@ -19,6 +19,15 @@ import Foundation import Common +protocol DataBrokerOperationsCollectionErrorDelegate: AnyObject { + func dataBrokerOperationsCollection(_ dataBrokerOperationsCollection: DataBrokerOperationsCollection, + didError error: Error, + whileRunningBrokerOperationData: BrokerOperationData, + withDataBrokerName dataBrokerName: String?) + func dataBrokerOperationsCollection(_ dataBrokerOperationsCollection: DataBrokerOperationsCollection, + didErrorBeforeStartingBrokerOperations error: Error) +} + final class DataBrokerOperationsCollection: Operation { enum OperationType { @@ -27,6 +36,9 @@ final class DataBrokerOperationsCollection: Operation { case all } + public var error: Error? + public weak var errorDelegate: DataBrokerOperationsCollectionErrorDelegate? + private let dataBrokerID: Int64 private let database: DataBrokerProtectionRepository private let id = UUID() @@ -126,8 +138,18 @@ final class DataBrokerOperationsCollection: Operation { return filteredAndSortedOperationsData } + // swiftlint:disable:next function_body_length private func runOperation() async { - let allBrokerProfileQueryData = database.fetchAllBrokerProfileQueryData() + let allBrokerProfileQueryData: [BrokerProfileQueryData] + + do { + allBrokerProfileQueryData = try database.fetchAllBrokerProfileQueryData() + } catch { + os_log("DataBrokerOperationsCollection error: runOperation, error: %{public}@", log: .error, error.localizedDescription) + errorDelegate?.dataBrokerOperationsCollection(self, didErrorBeforeStartingBrokerOperations: error) + return + } + let brokerProfileQueriesData = allBrokerProfileQueryData.filter { $0.dataBroker.id == dataBrokerID } let filteredAndSortedOperationsData = filterAndSortOperationsData(brokerProfileQueriesData: brokerProfileQueriesData, @@ -174,12 +196,11 @@ final class DataBrokerOperationsCollection: Operation { } catch { os_log("Error: %{public}@", log: .dataBrokerProtection, error.localizedDescription) - if let error = error as? DataBrokerProtectionError, - let dataBrokerName = brokerProfileQueriesData.first?.dataBroker.name { - pixelHandler.fire(.error(error: error, dataBroker: dataBrokerName)) - } else { - os_log("Cant handle error", log: .dataBrokerProtection) - } + self.error = error + errorDelegate?.dataBrokerOperationsCollection(self, + didError: error, + whileRunningBrokerOperationData: operationData, + withDataBrokerName: brokerProfileQueriesData.first?.dataBroker.name) } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift index f5c29ae8b7..f1e9a7b377 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProfileQueryOperationManager.swift @@ -112,7 +112,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } defer { - database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId) + try? database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId) os_log("Finished scan operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) notificationCenter.post(name: DataBrokerProtectionNotifications.didFinishScan, object: brokerProfileQueryData.dataBroker.name) } @@ -122,7 +122,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { do { let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .scanStarted) - database.add(event) + try database.add(event) let extractedProfiles = try await runner.scan(brokerProfileQueryData, stageCalculator: stageCalculator, showWebView: showWebView, shouldRunNextStep: shouldRunNextStep) os_log("Extracted profiles: %@", log: .dataBrokerProtection, extractedProfiles) @@ -130,12 +130,12 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { if !extractedProfiles.isEmpty { stageCalculator.fireScanSuccess(matchesFound: extractedProfiles.count) let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .matchesFound(count: extractedProfiles.count)) - database.add(event) + try database.add(event) for extractedProfile in extractedProfiles { // We check if the profile exists in the database. - let extractedProfilesForBroker = database.fetchExtractedProfiles(for: brokerId) + let extractedProfilesForBroker = try database.fetchExtractedProfiles(for: brokerId) let doesProfileExistsInDatabase = extractedProfilesForBroker.contains { $0.identifier == extractedProfile.identifier } // If the profile exists we do not create a new opt-out operation @@ -144,8 +144,8 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { if alreadyInDatabaseProfile.removedDate != nil { let reAppereanceEvent = HistoryEvent(extractedProfileId: extractedProfile.id, brokerId: brokerId, profileQueryId: profileQueryId, type: .reAppearence) eventPixels.fireReAppereanceEventPixel() - database.add(reAppereanceEvent) - database.updateRemovedDate(nil, on: id) + try database.add(reAppereanceEvent) + try database.updateRemovedDate(nil, on: id) } os_log("Extracted profile already exists in database: %@", log: .dataBrokerProtection, id.description) @@ -175,7 +175,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } else { stageCalculator.fireScanFailed() let event = HistoryEvent(brokerId: brokerId, profileQueryId: profileQueryId, type: .noMatchFound) - database.add(event) + try database.add(event) } // Check for removed profiles @@ -190,8 +190,8 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { for removedProfile in removedProfiles { if let extractedProfileId = removedProfile.id { let event = HistoryEvent(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutConfirmed) - database.add(event) - database.updateRemovedDate(Date(), on: extractedProfileId) + try database.add(event) + try database.updateRemovedDate(Date(), on: extractedProfileId) shouldSendProfileRemovedNotification = true try updateOperationDataDates( origin: .scan, @@ -204,7 +204,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { os_log("Profile removed from optOutsData: %@", log: .dataBrokerProtection, String(describing: removedProfile)) - if let attempt = database.fetchAttemptInformation(for: extractedProfileId), let attemptUUID = UUID(uuidString: attempt.attemptId) { + if let attempt = try database.fetchAttemptInformation(for: extractedProfileId), let attemptUUID = UUID(uuidString: attempt.attemptId) { let now = Date() let calculateDurationSinceLastStage = now.timeIntervalSince(attempt.lastStageDate) * 1000 let calculateDurationSinceStart = now.timeIntervalSince(attempt.startDate) * 1000 @@ -242,9 +242,9 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } private func sendProfileRemovedNotificationIfNecessary(userNotificationService: DataBrokerProtectionUserNotificationService, database: DataBrokerProtectionRepository) { - let savedExtractedProfiles = database.fetchAllBrokerProfileQueryData().flatMap { $0.extractedProfiles } - guard savedExtractedProfiles.count > 0 else { + guard let savedExtractedProfiles = try? database.fetchAllBrokerProfileQueryData().flatMap({ $0.extractedProfiles }), + savedExtractedProfiles.count > 0 else { return } @@ -292,7 +292,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { defer { os_log("Finished opt-out operation: %{public}@", log: .dataBrokerProtection, String(describing: brokerProfileQueryData.dataBroker.name)) - database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + try? database.updateLastRunDate(Date(), brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) do { try updateOperationDataDates( origin: .optOut, @@ -317,7 +317,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } do { - database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutStarted)) + try database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutStarted)) try await runner.optOut(profileQuery: brokerProfileQueryData, extractedProfile: extractedProfile, @@ -325,23 +325,23 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { showWebView: showWebView, shouldRunNextStep: shouldRunNextStep) - let tries = retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + let tries = try retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) stageDurationCalculator.fireOptOutValidate() stageDurationCalculator.fireOptOutSubmitSuccess(tries: tries) let updater = OperationPreferredDateUpdaterUseCase(database: database) - updater.updateChildrenBrokerForParentBroker(brokerProfileQueryData.dataBroker, + try updater.updateChildrenBrokerForParentBroker(brokerProfileQueryData.dataBroker, profileQueryId: profileQueryId) - database.addAttempt(extractedProfileId: extractedProfileId, + try database.addAttempt(extractedProfileId: extractedProfileId, attemptUUID: stageDurationCalculator.attemptId, dataBroker: stageDurationCalculator.dataBroker, lastStageDate: stageDurationCalculator.lastStateTime, startTime: stageDurationCalculator.startTime) - database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)) + try database.add(.init(extractedProfileId: extractedProfileId, brokerId: brokerId, profileQueryId: profileQueryId, type: .optOutRequested)) } catch { - let tries = retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) - stageDurationCalculator.fireOptOutFailure(tries: tries) + let tries = try? retriesCalculatorUseCase.calculateForOptOut(database: database, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + stageDurationCalculator.fireOptOutFailure(tries: tries ?? -1) handleOperationError( origin: .optOut, brokerId: brokerId, @@ -394,7 +394,7 @@ struct DataBrokerProfileQueryOperationManager: OperationsManager { } } - database.add(event) + try? database.add(event) do { try updateOperationDataDates( diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift index f3203334e2..79020cde0b 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/DataBrokerProtectionBrokerUpdater.swift @@ -20,20 +20,26 @@ import Foundation import Common protocol ResourcesRepository { - func fetchBrokerFromResourceFiles() -> [DataBroker]? + func fetchBrokerFromResourceFiles() throws -> [DataBroker]? } final class FileResources: ResourcesRepository { + enum FileResourcesError: Error { + case bundleResourceURLNil + } + private let fileManager: FileManager init(fileManager: FileManager = .default) { self.fileManager = fileManager } - func fetchBrokerFromResourceFiles() -> [DataBroker]? { + func fetchBrokerFromResourceFiles() throws -> [DataBroker]? { guard let resourceURL = Bundle.module.resourceURL else { - return nil + assertionFailure() + os_log("DataBrokerProtectionUpdater: error FileResources fetchBrokerFromResourceFiles, error: Bundle.module.resourceURL is nil", log: .error) + throw FileResourcesError.bundleResourceURLNil } do { @@ -47,10 +53,10 @@ final class FileResources: ResourcesRepository { $0.isJSON && !$0.hasFakePrefix } - return brokerJSONFiles.map(DataBroker.initFromResource(_:)) + return try brokerJSONFiles.map(DataBroker.initFromResource(_:)) } catch { - os_log("Error fetching brokers JSON files from resources", log: .dataBrokerProtection) - return nil + os_log("DataBrokerProtectionUpdater: error FileResources error: fetchBrokerFromResourceFiles, error: %{public}@", log: .error, error.localizedDescription) + throw error } } } @@ -97,15 +103,18 @@ public struct DataBrokerProtectionBrokerUpdater { private let resources: ResourcesRepository private let vault: any DataBrokerProtectionSecureVault private let appVersion: AppVersionNumberProvider + private let pixelHandler: EventMapping init(repository: BrokerUpdaterRepository = BrokerUpdaterUserDefaults(), resources: ResourcesRepository = FileResources(), vault: any DataBrokerProtectionSecureVault, - appVersion: AppVersionNumberProvider = AppVersionNumber()) { + appVersion: AppVersionNumberProvider = AppVersionNumber(), + pixelHandler: EventMapping = DataBrokerProtectionPixelsHandler()) { self.repository = repository self.resources = resources self.vault = vault self.appVersion = appVersion + self.pixelHandler = pixelHandler } public static func provide() -> DataBrokerProtectionBrokerUpdater? { @@ -118,13 +127,22 @@ public struct DataBrokerProtectionBrokerUpdater { } public func updateBrokers() { - guard let brokers = resources.fetchBrokerFromResourceFiles() else { return } + let brokers: [DataBroker]? + do { + brokers = try resources.fetchBrokerFromResourceFiles() + } catch { + os_log("DataBrokerProtectionBrokerUpdater updateBrokers, error: %{public}@", log: .error, error.localizedDescription) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionBrokerUpdater.updateBrokers")) + return + } + guard let brokers = brokers else { return } for broker in brokers { do { try update(broker) } catch { os_log("Error updating broker: %{public}@, with version: %{public}@", log: .dataBrokerProtection, broker.name, broker.version) + pixelHandler.fire(.generalError(error: error, functionOccurredIn: "DataBrokerProtectionBrokerUpdater.updateBrokers")) } } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift index 1b27e2025d..73cb7fb2b1 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationPreferredDateUpdater.swift @@ -33,7 +33,7 @@ protocol OperationPreferredDateUpdater { extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig) throws - func updateChildrenBrokerForParentBroker(_ parentBroker: DataBroker, profileQueryId: Int64) + func updateChildrenBrokerForParentBroker(_ parentBroker: DataBroker, profileQueryId: Int64) throws } struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { @@ -47,8 +47,8 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { extractedProfileId: Int64?, schedulingConfig: DataBrokerScheduleConfig) throws { - guard let brokerProfileQuery = database.brokerProfileQueryData(for: brokerId, - and: profileQueryId) else { return } + guard let brokerProfileQuery = try database.brokerProfileQueryData(for: brokerId, + and: profileQueryId) else { return } try updateScanOperationDataDates(origin: origin, brokerId: brokerId, @@ -70,16 +70,21 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { /// 1, This method fetches scan operations with the profileQueryId and with child sites of parentBrokerId /// 2. Then for each one it updates the preferredRunDate of the scan to its confirm scan - func updateChildrenBrokerForParentBroker(_ parentBroker: DataBroker, profileQueryId: Int64) { - let childBrokers = database.fetchChildBrokers(for: parentBroker.name) - - childBrokers.forEach { childBroker in - if let childBrokerId = childBroker.id { - let confirmOptOutScanDate = Date().addingTimeInterval(childBroker.schedulingConfig.confirmOptOutScan.hoursToSeconds) - database.updatePreferredRunDate(confirmOptOutScanDate, - brokerId: childBrokerId, - profileQueryId: profileQueryId) + func updateChildrenBrokerForParentBroker(_ parentBroker: DataBroker, profileQueryId: Int64) throws { + do { + let childBrokers = try database.fetchChildBrokers(for: parentBroker.name) + + try childBrokers.forEach { childBroker in + if let childBrokerId = childBroker.id { + let confirmOptOutScanDate = Date().addingTimeInterval(childBroker.schedulingConfig.confirmOptOutScan.hoursToSeconds) + try database.updatePreferredRunDate(confirmOptOutScanDate, + brokerId: childBrokerId, + profileQueryId: profileQueryId) + } } + } catch { + os_log("OperationPreferredDateUpdaterUseCase error: updateChildrenBrokerForParentBroker, error: %{public}@", log: .error, error.localizedDescription) + throw error } } @@ -102,10 +107,10 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } if newScanPreferredRunDate != currentScanPreferredRunDate { - updatePreferredRunDate(newScanPreferredRunDate, - brokerId: brokerId, - profileQueryId: profileQueryId, - extractedProfileId: nil) + try updatePreferredRunDate(newScanPreferredRunDate, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: nil) } } @@ -129,10 +134,10 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { } if newOptOutPreferredDate != currentOptOutPreferredRunDate { - updatePreferredRunDate(newOptOutPreferredDate, - brokerId: brokerId, - profileQueryId: profileQueryId, - extractedProfileId: extractedProfileId) + try updatePreferredRunDate(newOptOutPreferredDate, + brokerId: brokerId, + profileQueryId: profileQueryId, + extractedProfileId: extractedProfileId) } } @@ -146,11 +151,16 @@ struct OperationPreferredDateUpdaterUseCase: OperationPreferredDateUpdater { private func updatePreferredRunDate( _ date: Date?, brokerId: Int64, profileQueryId: Int64, - extractedProfileId: Int64?) { - if let extractedProfileId = extractedProfileId { - database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) - } else { - database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + extractedProfileId: Int64?) throws { + do { + if let extractedProfileId = extractedProfileId { + try database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + } else { + try database.updatePreferredRunDate(date, brokerId: brokerId, profileQueryId: profileQueryId) + } + } catch { + os_log("OperationPreferredDateUpdaterUseCase error: updatePreferredRunDate, error: %{public}@", log: .error, error.localizedDescription) + throw error } os_log("Updating preferredRunDate on operation with brokerId %{public}@ and profileQueryId %{public}@", log: .dataBrokerProtection, brokerId.description, profileQueryId.description) diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift index cca75f16b2..8035391f64 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/OperationRetriesCalculatorUseCase.swift @@ -20,14 +20,14 @@ import Foundation struct OperationRetriesCalculatorUseCase { - func calculateForScan(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64) -> Int { - let events = database.fetchScanHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId) + func calculateForScan(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64) throws -> Int { + let events = try database.fetchScanHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId) return events.filter { $0.type == .scanStarted }.count } - func calculateForOptOut(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) -> Int { - let events = database.fetchOptOutHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) + func calculateForOptOut(database: DataBrokerProtectionRepository, brokerId: Int64, profileQueryId: Int64, extractedProfileId: Int64) throws -> Int { + let events = try database.fetchOptOutHistoryEvents(brokerId: brokerId, profileQueryId: profileQueryId, extractedProfileId: extractedProfileId) return events.filter { $0.type == .optOutStarted }.count } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift index 9f278ab31a..d4d83f7b2a 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ParentChildRelationship/MismatchCalculatorUseCase.swift @@ -41,7 +41,14 @@ struct MismatchCalculatorUseCase { let pixelHandler: EventMapping func calculateMismatches() { - let brokerProfileQueryData = database.fetchAllBrokerProfileQueryData() + let brokerProfileQueryData: [BrokerProfileQueryData] + do { + brokerProfileQueryData = try database.fetchAllBrokerProfileQueryData() + } catch { + os_log("MismatchCalculatorUseCase error: calculateMismatches, error: %{public}@", log: .error, error.localizedDescription) + return + } + let parentBrokerProfileQueryData = brokerProfileQueryData.filter { $0.dataBroker.parent == nil } for parent in parentBrokerProfileQueryData { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift index 10d2bd799b..10bf5bb9e9 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Operations/ScanOperation.swift @@ -65,7 +65,7 @@ final class ScanOperation: DataBrokerOperation { self.cookieHandler = cookieHandler } - func run(inputValue: Void, + func run(inputValue: InputValue, webViewHandler: WebViewHandler? = nil, actionsHandler: ActionsHandler? = nil, showWebView: Bool) async throws -> [ExtractedProfile] { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift index f00192e769..e825e0eb33 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEngagementPixels.swift @@ -113,7 +113,7 @@ final class DataBrokerProtectionEngagementPixels { } func fireEngagementPixel(currentDate: Date = Date()) { - guard database.fetchProfile() != nil else { + guard (try? database.fetchProfile()) != nil else { os_log("No profile. We do not fire any pixel because we do not consider it an engaged user.", log: .dataBrokerProtection) return } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift index db8d89de19..589ab33afb 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionEventPixels.swift @@ -87,7 +87,14 @@ final class DataBrokerProtectionEventPixels { } private func fireWeeklyReportPixels() { - let data = database.fetchAllBrokerProfileQueryData() + let data: [BrokerProfileQueryData] + + do { + data = try database.fetchAllBrokerProfileQueryData() + } catch { + os_log("Database error: when attempting to fireWeeklyReportPixels, error: %{public}@", log: .error, error.localizedDescription) + return + } let dataInThePastWeek = data.filter(hadScanThisWeek(_:)) var newMatchesFoundInTheLastWeek = 0 diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift index 1be9422b34..e3d0594950 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Pixels/DataBrokerProtectionPixels.swift @@ -26,6 +26,7 @@ enum ErrorCategory: Equatable { case validationError case clientError(httpCode: Int) case serverError(httpCode: Int) + case databaseError(domain: String, code: Int) case unclassified var toString: String { @@ -35,6 +36,7 @@ enum ErrorCategory: Equatable { case .unclassified: return "unclassified" case .clientError(let httpCode): return "client-error-\(httpCode)" case .serverError(let httpCode): return "server-error-\(httpCode)" + case .databaseError(let domain, let code): return "database-error-\(domain)-\(code)" } } } @@ -62,6 +64,9 @@ public enum DataBrokerProtectionPixels { } case error(error: DataBrokerProtectionError, dataBroker: String) + case generalError(error: Error, functionOccurredIn: String) + case secureVaultInitError(error: Error) + case secureVaultError(error: Error) case parentChildMatches(parent: String, child: String, value: Int) // Stage Pixels @@ -165,6 +170,9 @@ extension DataBrokerProtectionPixels: PixelKitEvent { // Debug Pixels case .error: return "m_mac_data_broker_error" + case .generalError: return "m_mac_data_broker_error" + case .secureVaultInitError: return "m_mac_dbp_secure_vault_init_error" + case .secureVaultError: return "m_mac_dbp_secure_vault_error" case .backgroundAgentStarted: return "m_mac_dbp_background-agent_started" case .backgroundAgentStartedStoppingDueToAnotherInstanceRunning: return "m_mac_dbp_background-agent_started_stopping-due-to-another-instance-running" @@ -233,6 +241,8 @@ extension DataBrokerProtectionPixels: PixelKitEvent { } else { return ["dataBroker": dataBroker, "name": error.name] } + case .generalError(_, let functionOccurredIn): + return ["functionOccurredIn": functionOccurredIn] case .parentChildMatches(let parent, let child, let value): return ["parent": parent, "child": child, "value": String(value)] case .optOutStart(let dataBroker, let attemptId): @@ -302,8 +312,12 @@ extension DataBrokerProtectionPixels: PixelKitEvent { .dailyActiveUser, .weeklyActiveUser, .monthlyActiveUser, + .scanningEventNewMatch, - .scanningEventReAppearance: + .scanningEventReAppearance, + + .secureVaultInitError, + .secureVaultError: return [:] case .ipcServerRegister, .ipcServerStartScheduler, @@ -334,6 +348,11 @@ public class DataBrokerProtectionPixelsHandler: EventMapping Void)?) { } - func runQueuedOperations(showWebView: Bool, completion: ((Error?) -> Void)?) { } - func scanAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) { } + func optOutAllBrokers(showWebView: Bool, completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { } + func runQueuedOperations(showWebView: Bool, completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { } + func scanAllBrokers(showWebView: Bool, completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { } func runAllOperations(showWebView: Bool) { } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift index d6971d1a80..f10ca642e5 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionProcessor.swift @@ -55,13 +55,14 @@ final class DataBrokerProtectionProcessor { } // MARK: - Public functions - func runAllScanOperations(showWebView: Bool = false, completion: (() -> Void)? = nil) { + func runAllScanOperations(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)? = nil) { operationQueue.cancelAllOperations() runOperations(operationType: .scan, priorityDate: nil, - showWebView: showWebView) { + showWebView: showWebView) { errors in os_log("Scans done", log: .dataBrokerProtection) - completion?() + completion?(errors) self.calculateMisMatches() } } @@ -71,31 +72,34 @@ final class DataBrokerProtectionProcessor { mismatchUseCase.calculateMismatches() } - func runAllOptOutOperations(showWebView: Bool = false, completion: (() -> Void)? = nil) { + func runAllOptOutOperations(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)? = nil) { operationQueue.cancelAllOperations() runOperations(operationType: .optOut, priorityDate: nil, - showWebView: showWebView) { + showWebView: showWebView) { errors in os_log("Optouts done", log: .dataBrokerProtection) - completion?() + completion?(errors) } } - func runQueuedOperations(showWebView: Bool = false, completion: (() -> Void)? = nil ) { + func runQueuedOperations(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)? = nil ) { runOperations(operationType: .all, priorityDate: Date(), - showWebView: showWebView) { + showWebView: showWebView) { errors in os_log("Queued operations done", log: .dataBrokerProtection) - completion?() + completion?(errors) } } - func runAllOperations(showWebView: Bool = false, completion: (() -> Void)? = nil ) { + func runAllOperations(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)? = nil ) { runOperations(operationType: .all, priorityDate: nil, - showWebView: showWebView) { + showWebView: showWebView) { errors in os_log("Queued operations done", log: .dataBrokerProtection) - completion?() + completion?(errors) } } @@ -107,11 +111,11 @@ final class DataBrokerProtectionProcessor { private func runOperations(operationType: DataBrokerOperationsCollection.OperationType, priorityDate: Date?, showWebView: Bool, - completion: @escaping () -> Void) { + completion: @escaping ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)) { // Before running new operations we check if there is any updates to the broker files. if let vault = try? DataBrokerProtectionSecureVaultFactory.makeVault(errorReporter: nil) { - let brokerUpdater = DataBrokerProtectionBrokerUpdater(vault: vault) + let brokerUpdater = DataBrokerProtectionBrokerUpdater(vault: vault, pixelHandler: pixelHandler) brokerUpdater.checkForUpdatesInBrokerJSONFiles() } @@ -120,18 +124,30 @@ final class DataBrokerProtectionProcessor { // This will try to fire the event weekly report pixels eventPixels.tryToFireWeeklyPixels() - let brokersProfileData = database.fetchAllBrokerProfileQueryData() - let dataBrokerOperationCollections = createDataBrokerOperationCollections(from: brokersProfileData, - operationType: operationType, - priorityDate: priorityDate, - showWebView: showWebView) + let dataBrokerOperationCollections: [DataBrokerOperationsCollection] - for collection in dataBrokerOperationCollections { - operationQueue.addOperation(collection) + do { + let brokersProfileData = try database.fetchAllBrokerProfileQueryData() + dataBrokerOperationCollections = createDataBrokerOperationCollections(from: brokersProfileData, + operationType: operationType, + priorityDate: priorityDate, + showWebView: showWebView) + + for collection in dataBrokerOperationCollections { + operationQueue.addOperation(collection) + } + } catch { + os_log("DataBrokerProtectionProcessor error: runOperations, error: %{public}@", log: .error, error.localizedDescription) + operationQueue.addBarrierBlock { + completion(DataBrokerProtectionSchedulerErrorCollection(oneTimeError: error)) + } + return } operationQueue.addBarrierBlock { - completion() + let operationErrors = dataBrokerOperationCollections.compactMap { $0.error } + let errorCollection = operationErrors.count != 0 ? DataBrokerProtectionSchedulerErrorCollection(operationErrors: operationErrors) : nil + completion(errorCollection) } } @@ -157,6 +173,7 @@ final class DataBrokerProtectionProcessor { pixelHandler: pixelHandler, userNotificationService: userNotificationService, showWebView: showWebView) + collection.errorDelegate = self collections.append(collection) visitedDataBrokerIDs.insert(dataBrokerID) @@ -170,3 +187,22 @@ final class DataBrokerProtectionProcessor { os_log("Deinit DataBrokerProtectionProcessor", log: .dataBrokerProtection) } } + +extension DataBrokerProtectionProcessor: DataBrokerOperationsCollectionErrorDelegate { + + func dataBrokerOperationsCollection(_ dataBrokerOperationsCollection: DataBrokerOperationsCollection, didErrorBeforeStartingBrokerOperations error: Error) { + + } + + func dataBrokerOperationsCollection(_ dataBrokerOperationsCollection: DataBrokerOperationsCollection, + didError error: Error, + whileRunningBrokerOperationData: BrokerOperationData, + withDataBrokerName dataBrokerName: String?) { + if let error = error as? DataBrokerProtectionError, + let dataBrokerName = dataBrokerName { + pixelHandler.fire(.error(error: error, dataBroker: dataBrokerName)) + } else { + os_log("Cant handle error", log: .dataBrokerProtection) + } + } +} diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift index 7ad31ed349..a6e5f15362 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/Scheduler/DataBrokerProtectionScheduler.swift @@ -27,6 +27,23 @@ public enum DataBrokerProtectionSchedulerStatus: Codable { case running } +@objc +public class DataBrokerProtectionSchedulerErrorCollection: NSObject { + /* + This needs to be an NSObject (rather than a struct) so it can be represented in Objective C + for the IPC layer + */ + + public let oneTimeError: Error? + public let operationErrors: [Error]? + + public init(oneTimeError: Error? = nil, operationErrors: [Error]? = nil) { + self.oneTimeError = oneTimeError + self.operationErrors = operationErrors + super.init() + } +} + public protocol DataBrokerProtectionScheduler { var status: DataBrokerProtectionSchedulerStatus { get } @@ -35,9 +52,9 @@ public protocol DataBrokerProtectionScheduler { func startScheduler(showWebView: Bool) func stopScheduler() - func optOutAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) - func scanAllBrokers(showWebView: Bool, completion: ((Error?) -> Void)?) - func runQueuedOperations(showWebView: Bool, completion: ((Error?) -> Void)?) + func optOutAllBrokers(showWebView: Bool, completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) + func scanAllBrokers(showWebView: Bool, completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) + func runQueuedOperations(showWebView: Bool, completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) func runAllOperations(showWebView: Bool) } @@ -138,7 +155,17 @@ public final class DefaultDataBrokerProtectionScheduler: DataBrokerProtectionSch } self.status = .running os_log("Scheduler running...", log: .dataBrokerProtection) - self.dataBrokerProcessor.runQueuedOperations(showWebView: showWebView) { [weak self] in + self.dataBrokerProcessor.runQueuedOperations(showWebView: showWebView) { [weak self] errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Error during startScheduler in dataBrokerProcessor.runQueuedOperations(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + self?.pixelHandler.fire(.generalError(error: oneTimeError, functionOccurredIn: "DefaultDataBrokerProtectionScheduler.startScheduler")) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Operation error(s) during startScheduler in dataBrokerProcessor.runQueuedOperations(), count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + } + } self?.status = .idle completion(.finished) } @@ -154,40 +181,91 @@ public final class DefaultDataBrokerProtectionScheduler: DataBrokerProtectionSch public func runAllOperations(showWebView: Bool = false) { os_log("Running all operations...", log: .dataBrokerProtection) - self.dataBrokerProcessor.runAllOperations(showWebView: showWebView) + self.dataBrokerProcessor.runAllOperations(showWebView: showWebView) { [weak self] errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Error during DefaultDataBrokerProtectionScheduler.runAllOperations in dataBrokerProcessor.runAllOperations(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + self?.pixelHandler.fire(.generalError(error: oneTimeError, functionOccurredIn: "DefaultDataBrokerProtectionScheduler.runAllOperations")) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Operation error(s) during DefaultDataBrokerProtectionScheduler.runAllOperations in dataBrokerProcessor.runAllOperations(), count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + } + } + } } - public func runQueuedOperations(showWebView: Bool = false, completion: ((Error?) -> Void)? = nil) { + public func runQueuedOperations(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)? = nil) { os_log("Running queued operations...", log: .dataBrokerProtection) dataBrokerProcessor.runQueuedOperations(showWebView: showWebView, - completion: { completion?(nil) }) + completion: { [weak self] errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Error during DefaultDataBrokerProtectionScheduler.runQueuedOperations in dataBrokerProcessor.runQueuedOperations(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + self?.pixelHandler.fire(.generalError(error: oneTimeError, functionOccurredIn: "DefaultDataBrokerProtectionScheduler.runQueuedOperations")) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Operation error(s) during DefaultDataBrokerProtectionScheduler.runQueuedOperations in dataBrokerProcessor.runQueuedOperations(), count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + } + } + completion?(errors) + }) } - public func scanAllBrokers(showWebView: Bool = false, completion: ((Error?) -> Void)? = nil) { + public func scanAllBrokers(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)? = nil) { stopScheduler() userNotificationService.requestNotificationPermission() os_log("Scanning all brokers...", log: .dataBrokerProtection) - dataBrokerProcessor.runAllScanOperations(showWebView: showWebView) { [weak self] in + dataBrokerProcessor.runAllScanOperations(showWebView: showWebView) { [weak self] errors in guard let self = self else { return } self.startScheduler(showWebView: showWebView) self.userNotificationService.sendFirstScanCompletedNotification() - if self.dataManager.hasMatches() { + if let hasMatches = try? self.dataManager.hasMatches(), + hasMatches { self.userNotificationService.scheduleCheckInNotificationIfPossible() } - completion?(nil) + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Error during DefaultDataBrokerProtectionScheduler.scanAllBrokers in dataBrokerProcessor.runAllScanOperations(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + self.pixelHandler.fire(.generalError(error: oneTimeError, functionOccurredIn: "DefaultDataBrokerProtectionScheduler.scanAllBrokers")) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Operation error(s) during DefaultDataBrokerProtectionScheduler.scanAllBrokers in dataBrokerProcessor.runAllScanOperations(), count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + } + } + + completion?(errors) } } - public func optOutAllBrokers(showWebView: Bool = false, completion: ((Error?) -> Void)?) { + public func optOutAllBrokers(showWebView: Bool = false, + completion: ((DataBrokerProtectionSchedulerErrorCollection?) -> Void)?) { os_log("Opting out all brokers...", log: .dataBrokerProtection) self.dataBrokerProcessor.runAllOptOutOperations(showWebView: showWebView, - completion: { completion?(nil) }) + completion: { [weak self] errors in + if let errors = errors { + if let oneTimeError = errors.oneTimeError { + os_log("Error during DefaultDataBrokerProtectionScheduler.optOutAllBrokers in dataBrokerProcessor.runAllOptOutOperations(), error: %{public}@", log: .dataBrokerProtection, oneTimeError.localizedDescription) + self?.pixelHandler.fire(.generalError(error: oneTimeError, functionOccurredIn: "DefaultDataBrokerProtectionScheduler.optOutAllBrokers")) + } + if let operationErrors = errors.operationErrors, + operationErrors.count != 0 { + os_log("Operation error(s) during DefaultDataBrokerProtectionScheduler.optOutAllBrokers in dataBrokerProcessor.runAllOptOutOperations(), count: %{public}d", log: .dataBrokerProtection, operationErrors.count) + } + } + + completion?(errors) + }) } } diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift index 9f77b8a675..f99df1e614 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DBPUICommunicationLayer.swift @@ -23,9 +23,9 @@ import UserScript import Common protocol DBPUICommunicationDelegate: AnyObject { - func saveProfile() async -> Bool + func saveProfile() async throws func getUserProfile() -> DBPUIUserProfile? - func deleteProfileData() + func deleteProfileData() throws func addNameToCurrentUserProfile(_ name: DBPUIUserProfileName) -> Bool func setNameAtIndexInCurrentUserProfile(_ payload: DBPUINameAtIndex) -> Bool func removeNameAtIndexFromUserProfile(_ index: DBPUIIndex) -> Bool @@ -127,9 +127,13 @@ struct DBPUICommunicationLayer: Subfeature { func saveProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { os_log("Web UI requested to save the profile", log: .dataBrokerProtection) - let success = await delegate?.saveProfile() - - return DBPUIStandardResponse(version: Constants.version, success: success ?? false) + do { + try await delegate?.saveProfile() + return DBPUIStandardResponse(version: Constants.version, success: true) + } catch { + os_log("DBPUICommunicationLayer saveProfile, error: %{public}@", log: .error, error.localizedDescription) + return DBPUIStandardResponse(version: Constants.version, success: false) + } } func getCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { @@ -141,8 +145,13 @@ struct DBPUICommunicationLayer: Subfeature { } func deleteUserProfileData(params: Any, original: WKScriptMessage) async throws -> Encodable? { - delegate?.deleteProfileData() - return DBPUIStandardResponse(version: Constants.version, success: true) + do { + try delegate?.deleteProfileData() + return DBPUIStandardResponse(version: Constants.version, success: true) + } catch { + os_log("DBPUICommunicationLayer deleteUserProfileData, error: %{public}@", log: .error, error.localizedDescription) + return DBPUIStandardResponse(version: Constants.version, success: false) + } } func addNameToCurrentUserProfile(params: Any, original: WKScriptMessage) async throws -> Encodable? { diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift index 688de0476e..4f26b33b8e 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/UI/DataBrokerProtectionViewController.swift @@ -56,8 +56,10 @@ final public class DataBrokerProtectionViewController: NSViewController { prefs: prefs, webView: webView) + // Prepare the profile cache to avoid stuttering later + // It's in a task to avoid stuttering now Task { - _ = dataManager.fetchProfile(ignoresCache: true) + try? dataManager.prepareProfileCache() } super.init(nibName: nil, bundle: nil) diff --git a/LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml b/LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml new file mode 100644 index 0000000000..bf8a5655d9 --- /dev/null +++ b/LocalPackages/DataBrokerProtection/Tests/.swiftlint.yml @@ -0,0 +1,17 @@ +disabled_rules: + - file_length + - unused_closure_parameter + - type_name + - force_cast + - force_try + - function_body_length + - cyclomatic_complexity + - identifier_name + - blanket_disable_command + - type_body_length + - explicit_non_final_class + - enforce_os_log_wrapper + +large_tuple: + warning: 6 + error: 10 diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift index 616e1a5fa9..0668f38d35 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionProfileTests.swift @@ -162,7 +162,8 @@ final class DataBrokerProtectionProfileTests: XCTestCase { database: SecureStorageDatabaseProviderMock(), keystore: EmptySecureStorageKeyStoreProviderMock())) - let database = DataBrokerProtectionDatabase(vault: vault) + let database = DataBrokerProtectionDatabase(pixelHandler: MockDataBrokerProtectionPixelsHandler(), + vault: vault) let profile = DataBrokerProtectionProfile( names: [ @@ -175,7 +176,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _=await database.save(profile) + _ = try! await database.save(profile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) XCTAssertFalse(vault.wasDeleteProfileQueryCalled) @@ -188,7 +189,8 @@ final class DataBrokerProtectionProfileTests: XCTestCase { database: SecureStorageDatabaseProviderMock(), keystore: EmptySecureStorageKeyStoreProviderMock())) - let database = DataBrokerProtectionDatabase(vault: vault) + let database = DataBrokerProtectionDatabase(pixelHandler: MockDataBrokerProtectionPixelsHandler(), + vault: vault) vault.brokers = [DataBroker.mock] vault.profileQueries = [ProfileQuery.mock] @@ -217,7 +219,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _=await database.save(newProfile) + _ = try! await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertTrue(vault.wasUpdateProfileQueryCalled) @@ -231,7 +233,8 @@ final class DataBrokerProtectionProfileTests: XCTestCase { database: SecureStorageDatabaseProviderMock(), keystore: EmptySecureStorageKeyStoreProviderMock())) - let database = DataBrokerProtectionDatabase(vault: vault) + let database = DataBrokerProtectionDatabase(pixelHandler: MockDataBrokerProtectionPixelsHandler(), + vault: vault) vault.brokers = [DataBroker.mock] vault.profileQueries = [ProfileQuery.mock] @@ -259,7 +262,7 @@ final class DataBrokerProtectionProfileTests: XCTestCase { birthYear: 1980 ) - _ = await database.save(newProfile) + _ = try! await database.save(newProfile) XCTAssertTrue(vault.wasSaveProfileQueryCalled) XCTAssertFalse(vault.wasUpdateProfileQueryCalled) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStageDurationCalculatorTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStageDurationCalculatorTests.swift index 8b899e12a1..7ba868976f 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStageDurationCalculatorTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/DataBrokerProtectionStageDurationCalculatorTests.swift @@ -18,6 +18,7 @@ import BrowserServicesKit import Foundation +import SecureStorage import XCTest @testable import DataBrokerProtection @@ -120,6 +121,25 @@ final class DataBrokerProtectionStageDurationCalculatorTests: XCTestCase { } } + func testWhenErrorIsSecureVaultError_thenWeFireScanErorrPixelWithDatabaseErrorCategory() { + let sut = DataBrokerProtectionStageDurationCalculator(dataBroker: "broker", handler: handler) + let error = SecureStorageError.encodingFailed + + sut.fireScanError(error: error) + + XCTAssertTrue(MockDataBrokerProtectionPixelsHandler.lastPixelsFired.count == 1) + + if let failurePixel = MockDataBrokerProtectionPixelsHandler.lastPixelsFired.last{ + switch failurePixel { + case .scanError(_, _, let category, _): + XCTAssertEqual(category, "database-error-SecureVaultError-13") + default: XCTFail("The scan error pixel should be fired") + } + } else { + XCTFail("A pixel should be fired") + } + } + func testWhenErrorIsNotDBPErrorAndNotURL_thenWeFireScanErrorPixelWithUnclassifiedErrorCategory() { let sut = DataBrokerProtectionStageDurationCalculator(dataBroker: "broker", handler: handler) let error = NSError(domain: NSCocoaErrorDomain, code: -1) diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index 0581735b88..dc8d0eda8c 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -706,10 +706,8 @@ final class MockDatabase: DataBrokerProtectionRepository { callsList.filter { $0 }.count > 0 // If one value is true. The database was called } - func save(_ profile: DataBrokerProtectionProfile) -> Bool { + func save(_ profile: DataBrokerProtectionProfile) throws { wasSaveProfileCalled = true - - return true } func fetchProfile() -> DataBrokerProtectionProfile? { diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift index 369f80ee32..c95e90e182 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/OperationPreferredDateUpdaterTests.swift @@ -46,7 +46,7 @@ final class OperationPreferredDateUpdaterTests: XCTestCase { ) databaseMock.childBrokers = [childBroker] - sut.updateChildrenBrokerForParentBroker(.mock, profileQueryId: profileQueryId) + XCTAssertNoThrow(try sut.updateChildrenBrokerForParentBroker(.mock, profileQueryId: profileQueryId)) XCTAssertTrue(databaseMock.wasUpdatedPreferredRunDateForScanCalled) XCTAssertEqual(databaseMock.lastParentBrokerWhereChildSitesWhereFetched, "Test broker") @@ -57,7 +57,7 @@ final class OperationPreferredDateUpdaterTests: XCTestCase { func testWhenParentBrokerHasNoChildsites_thenNoCallsToTheDatabaseAreDone() { let sut = OperationPreferredDateUpdaterUseCase(database: databaseMock) - sut.updateChildrenBrokerForParentBroker(.mock, profileQueryId: 1) + XCTAssertNoThrow(try sut.updateChildrenBrokerForParentBroker(.mock, profileQueryId: 1)) XCTAssertFalse(databaseMock.wasDatabaseCalled) } From 773d073bf9734c7c8389a7bbc6ebadab563b5faa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mariusz=20=C5=9Apiewak?= Date: Tue, 9 Apr 2024 15:23:46 +0200 Subject: [PATCH 10/40] Keep windows miniaturized upon relaunching (#2569) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/276630244458377/1206774148393444/f Tech Design URL: CC: **Description**: Stores `isMiniaturized` property in `WindowRestorationItem` and applies to respective window during launch. **Steps to test this PR**: 1. Open a bunch of windows (preferably with a loaded URL). 2. Minimize some of them. 3. Restart the app. 4. Previously miniaturized windows should reopen in the same state (visible only in dock). — ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- .../StateRestoration/WindowManager+StateRestoration.swift | 7 ++++++- DuckDuckGo/Windows/View/WindowsManager.swift | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift b/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift index f9c84e5055..aaa38da89c 100644 --- a/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift +++ b/DuckDuckGo/StateRestoration/WindowManager+StateRestoration.swift @@ -52,7 +52,7 @@ extension WindowsManager { } private class func setUpWindow(from item: WindowRestorationItem) { - guard let window = openNewWindow(with: item.model, showWindow: true) else { return } + guard let window = openNewWindow(with: item.model, showWindow: !item.isMiniaturized, isMiniaturized: item.isMiniaturized) else { return } window.setContentSize(item.frame.size) window.setFrameOrigin(item.frame.origin) } @@ -135,11 +135,13 @@ final class WindowRestorationItem: NSObject, NSSecureCoding { private enum NSSecureCodingKeys { static let frame = "frame" static let model = "model" + static let isMiniaturized = "isMiniaturized" } let model: TabCollectionViewModel let frame: NSRect + let isMiniaturized: Bool @MainActor init?(windowController: MainWindowController) { @@ -150,6 +152,7 @@ final class WindowRestorationItem: NSObject, NSSecureCoding { self.frame = windowController.window!.frame self.model = windowController.mainViewController.tabCollectionViewModel + self.isMiniaturized = windowController.window!.isMiniaturized } static var supportsSecureCoding: Bool { true } @@ -161,10 +164,12 @@ final class WindowRestorationItem: NSObject, NSSecureCoding { } self.model = model self.frame = coder.decodeRect(forKey: NSSecureCodingKeys.frame) + self.isMiniaturized = coder.decodeBool(forKey: NSSecureCodingKeys.isMiniaturized) } func encode(with coder: NSCoder) { coder.encode(frame, forKey: NSSecureCodingKeys.frame) coder.encode(model, forKey: NSSecureCodingKeys.model) + coder.encode(isMiniaturized, forKey: NSSecureCodingKeys.isMiniaturized) } } diff --git a/DuckDuckGo/Windows/View/WindowsManager.swift b/DuckDuckGo/Windows/View/WindowsManager.swift index 245b36ed0c..007f850990 100644 --- a/DuckDuckGo/Windows/View/WindowsManager.swift +++ b/DuckDuckGo/Windows/View/WindowsManager.swift @@ -61,7 +61,8 @@ final class WindowsManager { contentSize: NSSize? = nil, showWindow: Bool = true, popUp: Bool = false, - lazyLoadTabs: Bool = false) -> MainWindow? { + lazyLoadTabs: Bool = false, + isMiniaturized: Bool = false) -> MainWindow? { let mainWindowController = makeNewWindow(tabCollectionViewModel: tabCollectionViewModel, popUp: popUp, burnerMode: burnerMode, @@ -71,6 +72,8 @@ final class WindowsManager { mainWindowController.window?.setContentSize(contentSize) } + mainWindowController.window?.setIsMiniaturized(isMiniaturized) + if let droppingPoint { mainWindowController.window?.setFrameOrigin(droppingPoint: droppingPoint) From 5be1c5af53c9b01a27099170c08d82fdd016f04b Mon Sep 17 00:00:00 2001 From: Halle <378795+Halle@users.noreply.github.com> Date: Tue, 9 Apr 2024 06:25:10 -0700 Subject: [PATCH 11/40] Adds a series of UI tests for Bookmarks and Favorites Task/Issue URL: https://app.asana.com/0/1199230911884351/1205717021705367/f Tech Design URL: CC: **Description**: Adds a series of UI tests for Bookmarks and Favorites **Steps to test this PR**: 1. Open the scheme **UI Tests** 2. Navigate to the test pane 3. Run BookmarksAndFavoritesTests **Note**: If this builds a `review` build that is treated as a first run on your system during every test (for instance, asking you where to put the application), please **build and run** the scheme once instead of running its tests, and go through the new app install first run steps once before running the tests, and answer any first install questions. **UI Tests general guidelines for everyone**: it unfortunately isn't possible to multitask while running UI tests on your local machine, because you will change or intercept screen interactions that the tests depend on. If you experience failures in your local environment, please always send the `xcresult` bundle so it is possible to view what happened differently on your machine, even if it seems like the same failure as a previous failure (it may be subtly different). Since the `xcresult` bundle will include a screen recording, for your privacy, please consider things like your chats, calls, and open documents that you don't want to send in a screen recording when you are running the tests. Thank you very much for your help! --- DuckDuckGo.xcodeproj/project.pbxproj | 4 + .../Bookmarks/Services/ContextualMenu.swift | 5 + ...kmarkManagementSidebarViewController.swift | 1 + .../View/BookmarkOutlineCellView.swift | 1 + .../View/BookmarkTableCellView.swift | 4 + .../Extensions/NSMenuItemExtension.swift | 5 + DuckDuckGo/HomePage/View/FavoritesView.swift | 16 +- DuckDuckGo/Menus/MainMenu.swift | 3 +- .../AddressBarButtonsViewController.swift | 7 +- .../NavigationBar/View/MoreOptionsMenu.swift | 3 +- .../View/PreferencesAppearanceView.swift | 2 +- .../TabExtensions/ContextMenuManager.swift | 2 +- UITests/AutocompleteTests.swift | 1 - UITests/BookmarksAndFavoritesTests.swift | 724 ++++++++++++++++++ UITests/BookmarksBarTests.swift | 1 - UITests/Common/UITests.swift | 2 +- UITests/Common/XCUIElementExtension.swift | 16 + 17 files changed, 780 insertions(+), 17 deletions(-) create mode 100644 UITests/BookmarksAndFavoritesTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2bbc99a10b..dfa32b6122 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3291,6 +3291,7 @@ EE339228291BDEFD009F62C1 /* JSAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE339227291BDEFD009F62C1 /* JSAlertController.swift */; }; EE3424602BA0853900173B1B /* VPNUninstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE34245D2BA0853900173B1B /* VPNUninstaller.swift */; }; EE3424612BA0853900173B1B /* VPNUninstaller.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE34245D2BA0853900173B1B /* VPNUninstaller.swift */; }; + EE54F7B32BBFEA49006218DB /* BookmarksAndFavoritesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE54F7B22BBFEA48006218DB /* BookmarksAndFavoritesTests.swift */; }; EE66418C2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */; }; EE66418D2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */; }; EE66666F2B56EDE4001D898D /* VPNLocationsHostingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */; }; @@ -4772,6 +4773,7 @@ EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindInPageTests.swift; sourceTree = ""; }; EE339227291BDEFD009F62C1 /* JSAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSAlertController.swift; sourceTree = ""; }; EE34245D2BA0853900173B1B /* VPNUninstaller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNUninstaller.swift; sourceTree = ""; }; + EE54F7B22BBFEA48006218DB /* BookmarksAndFavoritesTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksAndFavoritesTests.swift; sourceTree = ""; }; EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift"; sourceTree = ""; }; EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationsHostingViewController.swift; sourceTree = ""; }; EE7F74902BB5D76600CD9456 /* BookmarksBarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarTests.swift; sourceTree = ""; }; @@ -6556,6 +6558,7 @@ children = ( EEBCE6802BA444FA00B9DF00 /* Common */, EED735352BB46B6000F173D6 /* AutocompleteTests.swift */, + EE54F7B22BBFEA48006218DB /* BookmarksAndFavoritesTests.swift */, EE7F74902BB5D76600CD9456 /* BookmarksBarTests.swift */, EE02D41B2BB460A600DBE6B3 /* BrowsingHistoryTests.swift */, EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */, @@ -12279,6 +12282,7 @@ EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */, EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */, EE02D4212BB460FE00DBE6B3 /* StringExtension.swift in Sources */, + EE54F7B32BBFEA49006218DB /* BookmarksAndFavoritesTests.swift in Sources */, EE02D4222BB4611A00DBE6B3 /* TestsURLExtension.swift in Sources */, 7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */, EEBCE6832BA463DD00B9DF00 /* NSImageExtensions.swift in Sources */, diff --git a/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift b/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift index f79e265d96..eb77fd956f 100644 --- a/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift +++ b/DuckDuckGo/Bookmarks/Services/ContextualMenu.swift @@ -163,11 +163,15 @@ private extension ContextualMenu { static func addBookmarkToFavoritesMenuItem(isFavorite: Bool, bookmark: Bookmark?) -> NSMenuItem { let title = isFavorite ? UserText.removeFromFavorites : UserText.addToFavorites return menuItem(title, #selector(BookmarkMenuItemSelectors.toggleBookmarkAsFavorite(_:)), bookmark) + .withAccessibilityIdentifier(isFavorite == false ? "ContextualMenu.addBookmarkToFavoritesMenuItem" : + "ContextualMenu.removeBookmarkFromFavoritesMenuItem") } static func addBookmarksToFavoritesMenuItem(bookmarks: [Bookmark], allFavorites: Bool) -> NSMenuItem { let title = allFavorites ? UserText.removeFromFavorites : UserText.addToFavorites + let accessibilityValue = allFavorites ? "Favorited" : "Unfavorited" return menuItem(title, #selector(BookmarkMenuItemSelectors.toggleBookmarkAsFavorite(_:)), bookmarks) + .withAccessibilityIdentifier("ContextualMenu.addBookmarksToFavoritesMenuItem").withAccessibilityValue(accessibilityValue) } static func editBookmarkMenuItem(bookmark: Bookmark?) -> NSMenuItem { @@ -180,6 +184,7 @@ private extension ContextualMenu { static func deleteBookmarkMenuItem(bookmark: Bookmark?) -> NSMenuItem { menuItem(UserText.bookmarksBarContextMenuDelete, #selector(BookmarkMenuItemSelectors.deleteBookmark(_:)), bookmark) + .withAccessibilityIdentifier("ContextualMenu.deleteBookmark") } static func moveToEndMenuItem(entity: BaseBookmarkEntity?, parent: BookmarkFolder?) -> NSMenuItem { diff --git a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift index 53e502383b..f567a9f124 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkManagementSidebarViewController.swift @@ -89,6 +89,7 @@ final class BookmarkManagementSidebarViewController: NSViewController { tabSwitcherButton.menu = NSMenu { for content in Tab.TabContent.displayableTabTypes { NSMenuItem(title: content.title!, representedObject: content) + .withAccessibilityIdentifier("BookmarkManagementSidebarViewController.\(content.title!)") } } diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift index 603849bbbf..06b868c826 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift @@ -82,6 +82,7 @@ final class BookmarkOutlineCellView: NSTableCellView { faviconImageView.imageScaling = .scaleProportionallyDown faviconImageView.wantsLayer = true faviconImageView.layer?.cornerRadius = 2.0 + faviconImageView.setAccessibilityIdentifier("BookmarkOutlineCellView.favIconImageView") titleLabel.translatesAutoresizingMaskIntoConstraints = false titleLabel.isEditable = false diff --git a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift index 8bfdc3c4fb..3a3d3ea0d9 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkTableCellView.swift @@ -129,6 +129,7 @@ final class BookmarkTableCellView: NSTableCellView { menuButton.translatesAutoresizingMaskIntoConstraints = false menuButton.isBordered = false menuButton.isHidden = true + menuButton.setAccessibilityIdentifier("BookmarkTableCellView.menuButton") } private func setupLayout() { @@ -209,11 +210,14 @@ final class BookmarkTableCellView: NSTableCellView { faviconImageView.image = bookmark.favicon(.small) ?? .bookmarkDefaultFavicon + faviconImageView.setAccessibilityIdentifier("BookmarkTableCellView.favIconImageView") if bookmark.isFavorite { accessoryImageView.isHidden = false } accessoryImageView.image = bookmark.isFavorite ? .favoriteFilledBorder : nil + accessoryImageView.setAccessibilityIdentifier("BookmarkTableCellView.accessoryImageView") + accessoryImageView.setAccessibilityValue(bookmark.isFavorite ? "Favorited" : "Unfavorited") titleLabel.stringValue = bookmark.title primaryTitleLabelValue = bookmark.title tertiaryTitleLabelValue = bookmark.url diff --git a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift index b3f0870252..dfe76b5092 100644 --- a/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSMenuItemExtension.swift @@ -110,6 +110,11 @@ extension NSMenuItem { return self } + func withAccessibilityValue(_ accessibilityValue: String) -> NSMenuItem { + self.setAccessibilityValue(accessibilityValue) + return self + } + @discardableResult func withImage(_ image: NSImage?) -> NSMenuItem { self.image = image diff --git a/DuckDuckGo/HomePage/View/FavoritesView.swift b/DuckDuckGo/HomePage/View/FavoritesView.swift index 52e7f585d1..39f74b58cf 100644 --- a/DuckDuckGo/HomePage/View/FavoritesView.swift +++ b/DuckDuckGo/HomePage/View/FavoritesView.swift @@ -324,12 +324,12 @@ struct Favorite: View { .link { model.open(bookmark) }.contextMenu(ContextMenu(menuItems: { - Button(UserText.openInNewTab, action: { model.openInNewTab(bookmark) }) - Button(UserText.openInNewWindow, action: { model.openInNewWindow(bookmark) }) + Button(UserText.openInNewTab, action: { model.openInNewTab(bookmark) }).accessibilityIdentifier("HomePage.Views.openInNewTab") + Button(UserText.openInNewWindow, action: { model.openInNewWindow(bookmark) }).accessibilityIdentifier("HomePage.Views.openInNewWindow") Divider() - Button(UserText.edit, action: { model.edit(bookmark) }) - Button(UserText.removeFavorite, action: { model.removeFavorite(bookmark) }) - Button(UserText.deleteBookmark, action: { model.deleteBookmark(bookmark) }) + Button(UserText.edit, action: { model.edit(bookmark) }).accessibilityIdentifier("HomePage.Views.editBookmark") + Button(UserText.removeFavorite, action: { model.removeFavorite(bookmark) }).accessibilityIdentifier("HomePage.Views.removeFavorite") + Button(UserText.deleteBookmark, action: { model.deleteBookmark(bookmark) }).accessibilityIdentifier("HomePage.Views.deleteBookmark") })) } @@ -344,13 +344,13 @@ extension HomePage.Models.FavoriteModel { var favoriteView: some View { switch favoriteType { case .bookmark(let bookmark): - HomePage.Views.Favorite(bookmark: bookmark) + HomePage.Views.Favorite(bookmark: bookmark)?.accessibilityIdentifier("HomePage.Models.FavoriteModel.\(bookmark.title)") case .addButton: - HomePage.Views.FavoritesGridAddButton() + HomePage.Views.FavoritesGridAddButton().accessibilityIdentifier("HomePage.Models.FavoriteModel.addButton") case .ghostButton: - HomePage.Views.FavoritesGridGhostButton() + HomePage.Views.FavoritesGridGhostButton().accessibilityIdentifier("HomePage.Models.FavoriteModel.ghostButton") } } } diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index 31d25d3d31..d5bdd2d495 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -69,7 +69,7 @@ import SubscriptionUI var forwardMenuItem: NSMenuItem { historyMenu.forwardMenuItem } // MARK: Bookmarks - let manageBookmarksMenuItem = NSMenuItem(title: UserText.mainMenuHistoryManageBookmarks, action: #selector(MainViewController.showManageBookmarks)) + let manageBookmarksMenuItem = NSMenuItem(title: UserText.mainMenuHistoryManageBookmarks, action: #selector(MainViewController.showManageBookmarks)).withAccessibilityIdentifier("MainMenu.manageBookmarksMenuItem") var bookmarksMenuToggleBookmarksBarMenuItem = NSMenuItem(title: "BookmarksBarMenuPlaceholder", action: #selector(MainViewController.toggleBookmarksBarFromMenu), keyEquivalent: "B") let importBookmarksMenuItem = NSMenuItem(title: UserText.importBookmarks, action: #selector(AppDelegate.openImportBrowserDataWindow)) let bookmarksMenu = NSMenu(title: UserText.bookmarks) @@ -306,6 +306,7 @@ import SubscriptionUI .submenu(favoritesMenu.buildItems { NSMenuItem(title: UserText.mainMenuHistoryFavoriteThisPage, action: #selector(MainViewController.favoriteThisPage)) .withImage(.favorite) + .withAccessibilityIdentifier("MainMenu.favoriteThisPage") NSMenuItem.separator() }) .withImage(.favorite) diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 7afc044118..7bb06c097f 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -272,7 +272,7 @@ final class AddressBarButtonsViewController: NSViewController { private func updateBookmarkButtonVisibility() { guard view.window?.isPopUpWindow == false else { return } - bookmarkButton.setAccessibilityIdentifier("Bookmarks Button") + bookmarkButton.setAccessibilityIdentifier("AddressBarButtonsViewController.bookmarkButton") let hasEmptyAddressBar = textFieldValue?.isEmpty ?? true var showBookmarkButton: Bool { guard let tabViewModel, tabViewModel.canBeBookmarked else { return false } @@ -727,15 +727,18 @@ final class AddressBarButtonsViewController: NSViewController { private func updateBookmarkButtonImage(isUrlBookmarked: Bool = false) { if let url = tabViewModel?.tab.content.url, - isUrlBookmarked || bookmarkManager.isUrlBookmarked(url: url) { + isUrlBookmarked || bookmarkManager.isUrlBookmarked(url: url) + { bookmarkButton.image = .bookmarkFilled bookmarkButton.mouseOverTintColor = NSColor.bookmarkFilledTint bookmarkButton.toolTip = UserText.editBookmarkTooltip + bookmarkButton.setAccessibilityValue("Bookmarked") } else { bookmarkButton.mouseOverTintColor = nil bookmarkButton.image = .bookmark bookmarkButton.contentTintColor = nil bookmarkButton.toolTip = UserText.addBookmarkTooltip + bookmarkButton.setAccessibilityValue("Unbookmarked") } } diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index ed8a2e2b76..e784fccf14 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -270,7 +270,7 @@ final class MoreOptionsMenu: NSMenu { .targetting(self) .withImage(.bookmarks) .withSubmenu(bookmarksSubMenu) - + .withAccessibilityIdentifier("MoreOptionsMenu.openBookmarks") addItem(withTitle: UserText.downloads, action: #selector(openDownloads), keyEquivalent: "j") .targetting(self) .withImage(.downloads) @@ -641,6 +641,7 @@ final class BookmarksSubMenu: NSMenu { let bookmarkPageItem = addItem(withTitle: UserText.bookmarkThisPage, action: #selector(MoreOptionsMenu.bookmarkPage(_:)), keyEquivalent: "d") .withModifierMask([.command]) .targetting(target) + .withAccessibilityIdentifier("MoreOptionsMenu.bookmarkPage") bookmarkPageItem.isEnabled = tabCollectionViewModel.selectedTabViewModel?.canBeBookmarked == true diff --git a/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift b/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift index a7b30dae90..c44ff7c3bd 100644 --- a/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesAppearanceView.swift @@ -106,7 +106,7 @@ extension Preferences { if model.isContinueSetUpAvailable { ToggleMenuItem(UserText.newTabSetUpSectionTitle, isOn: $model.isContinueSetUpVisible) } - ToggleMenuItem(UserText.newTabFavoriteSectionTitle, isOn: $model.isFavoriteVisible) + ToggleMenuItem(UserText.newTabFavoriteSectionTitle, isOn: $model.isFavoriteVisible).accessibilityIdentifier("Preferences.AppearanceView.showFavoritesToggle") ToggleMenuItem(UserText.newTabRecentActivitySectionTitle, isOn: $model.isRecentActivityVisible) } diff --git a/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift b/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift index 787ac89300..e966c8b7a0 100644 --- a/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift +++ b/DuckDuckGo/Tab/TabExtensions/ContextMenuManager.swift @@ -234,7 +234,7 @@ private extension ContextMenuManager { } func bookmarkPageMenuItem() -> NSMenuItem { - NSMenuItem(title: UserText.bookmarkPage, action: #selector(MainViewController.bookmarkThisPage), target: nil, keyEquivalent: "") + NSMenuItem(title: UserText.bookmarkPage, action: #selector(MainViewController.bookmarkThisPage), target: nil, keyEquivalent: "").withAccessibilityIdentifier("ContextMenuManager.bookmarkPageMenuItem") } func openLinkInNewWindowMenuItem(from item: NSMenuItem) -> NSMenuItem { diff --git a/UITests/AutocompleteTests.swift b/UITests/AutocompleteTests.swift index 2ef08b7e8c..4c730f0f80 100644 --- a/UITests/AutocompleteTests.swift +++ b/UITests/AutocompleteTests.swift @@ -16,7 +16,6 @@ // limitations under the License. // -import Common import XCTest class AutocompleteTests: XCTestCase { diff --git a/UITests/BookmarksAndFavoritesTests.swift b/UITests/BookmarksAndFavoritesTests.swift new file mode 100644 index 0000000000..bf06b4aac6 --- /dev/null +++ b/UITests/BookmarksAndFavoritesTests.swift @@ -0,0 +1,724 @@ +// +// BookmarksAndFavoritesTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest + +class BookmarksAndFavoritesTests: XCTestCase { + private var app: XCUIApplication! + private var pageTitle: String! + private var urlForBookmarksBar: URL! + private let titleStringLength = 12 + + private var addressBarBookmarkButton: XCUIElement! + private var addressBarTextField: XCUIElement! + private var bookmarkDialogBookmarkFolderDropdown: XCUIElement! + private var bookmarkPageContextMenuItem: XCUIElement! + private var bookmarkPageMenuItem: XCUIElement! + private var bookmarksBarCollectionView: XCUIElement! + private var bookmarksDialogAddToFavoritesCheckbox: XCUIElement! + private var bookmarksManagementAccessoryImageView: XCUIElement! + private var bookmarksMenu: XCUIElement! + private var bookmarksTabPopup: XCUIElement! + private var bookmarkTableCellViewFavIconImageView: XCUIElement! + private var bookmarkTableCellViewMenuButton: XCUIElement! + private var contextualMenuAddBookmarkToFavoritesMenuItem: XCUIElement! + private var contextualMenuDeleteBookmarkMenuItem: XCUIElement! + private var contextualMenuRemoveBookmarkFromFavoritesMenuItem: XCUIElement! + private var defaultBookmarkDialogButton: XCUIElement! + private var defaultBookmarkOtherButton: XCUIElement! + private var favoriteGridAddFavoriteButton: XCUIElement! + private var favoriteThisPageMenuItem: XCUIElement! + private var manageBookmarksMenuItem: XCUIElement! + private var openBookmarksMenuItem: XCUIElement! + private var optionsButton: XCUIElement! + private var removeFavoritesContextMenuItem: XCUIElement! + private var resetBookMarksMenuItem: XCUIElement! + private var settingsAppearanceButton: XCUIElement! + private var showBookmarksBarPreferenceToggle: XCUIElement! + private var showBookmarksBarAlways: XCUIElement! + private var showBookmarksBarPopup: XCUIElement! + private var showFavoritesPreferenceToggle: XCUIElement! + + override func setUpWithError() throws { + continueAfterFailure = false + app = XCUIApplication() + app.launchEnvironment["UITEST_MODE"] = "1" + pageTitle = UITests.randomPageTitle(length: titleStringLength) + urlForBookmarksBar = UITests.simpleServedPage(titled: pageTitle) + addressBarBookmarkButton = app.buttons["AddressBarButtonsViewController.bookmarkButton"] + addressBarTextField = app.windows.textFields["AddressBarViewController.addressBarTextField"] + bookmarkDialogBookmarkFolderDropdown = app.popUpButtons["bookmark.add.folder.dropdown"] + bookmarkPageContextMenuItem = app.menuItems["ContextMenuManager.bookmarkPageMenuItem"] + bookmarkPageMenuItem = app.menuItems["MoreOptionsMenu.bookmarkPage"] + bookmarksBarCollectionView = app.collectionViews["BookmarksBarViewController.bookmarksBarCollectionView"] + bookmarksDialogAddToFavoritesCheckbox = app.checkBoxes["bookmark.add.add.to.favorites.button"] + bookmarksManagementAccessoryImageView = app.images["BookmarkTableCellView.accessoryImageView"] + bookmarksMenu = app.menuBarItems["Bookmarks"] + bookmarksTabPopup = app.popUpButtons["Bookmarks"] + bookmarkTableCellViewFavIconImageView = app.images["BookmarkTableCellView.favIconImageView"] + bookmarkTableCellViewMenuButton = app.buttons["BookmarkTableCellView.menuButton"] + contextualMenuAddBookmarkToFavoritesMenuItem = app.menuItems["ContextualMenu.addBookmarkToFavoritesMenuItem"] + contextualMenuDeleteBookmarkMenuItem = app.menuItems["ContextualMenu.deleteBookmark"] + contextualMenuRemoveBookmarkFromFavoritesMenuItem = app.menuItems["ContextualMenu.removeBookmarkFromFavoritesMenuItem"] + defaultBookmarkDialogButton = app.buttons["BookmarkDialogButtonsView.defaultButton"] + defaultBookmarkOtherButton = app.buttons["BookmarkDialogButtonsView.otherButton"] + favoriteGridAddFavoriteButton = app.staticTexts["HomePage.Models.FavoriteModel.addButton"] + favoriteThisPageMenuItem = app.menuItems["MainMenu.favoriteThisPage"] + manageBookmarksMenuItem = app.menuItems["MainMenu.manageBookmarksMenuItem"] + openBookmarksMenuItem = app.menuItems["MoreOptionsMenu.openBookmarks"] + optionsButton = app.buttons["NavigationBarViewController.optionsButton"] + removeFavoritesContextMenuItem = app.menuItems["HomePage.Views.removeFavorite"] + resetBookMarksMenuItem = app.menuItems["MainMenu.resetBookmarks"] + settingsAppearanceButton = app.buttons["PreferencesSidebar.appearanceButton"] + showBookmarksBarAlways = app.menuItems["Preferences.AppearanceView.showBookmarksBarAlways"] + showBookmarksBarPopup = app.popUpButtons["Preferences.AppearanceView.showBookmarksBarPopUp"] + showBookmarksBarPreferenceToggle = app.checkBoxes["Preferences.AppearanceView.showBookmarksBarPreferenceToggle"] + showFavoritesPreferenceToggle = app.checkBoxes["Preferences.AppearanceView.showFavoritesToggle"] + + app.launch() + resetBookmarks() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Let's enforce a single window + app.typeKey("n", modifierFlags: .command) + } + + func test_bookmarks_canBeAddedTo_withContextClickBookmarkThisPage() { + openSiteToBookmark(bookmarkingViaDialog: false, escapingDialog: false) + app.windows.webViews[pageTitle].rightClick() + bookmarkPageContextMenuItem.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // Check Add Bookmark dialog for existence but don't click on it + defaultBookmarkDialogButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog option button didn't appear with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + addressBarBookmarkButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The address bar bookmark button didn't appear with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + bookmarkDialogBookmarkFolderDropdown.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog's bookmark folder dropdown didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarkDialogBookmarkFolderDropdownValue = try? XCTUnwrap( // Bookmark dialog must default to "Bookmarks" folder + bookmarkDialogBookmarkFolderDropdown.value as? String, + "It wasn't possible to get the value of the \"Add bookmark\" dialog's bookmark folder dropdown as String" + ) + XCTAssertEqual( + bookmarkDialogBookmarkFolderDropdownValue, + "Bookmarks", + "The accessibility value of the \"Add bookmark\" dialog's bookmark folder dropdown must be \"Bookmarks\"." + ) + let addressBarBookmarkButtonValue = try? XCTUnwrap( + addressBarBookmarkButton.value as? String, + "It wasn't possible to get the value of the address bar bookmark button as String" + ) + + XCTAssertEqual( // The bookmark icon is already in a filled state and it isn't necessary to click the add button + addressBarBookmarkButtonValue, + "Bookmarked", + "The accessibility value of the address bar bookmark button must be \"Bookmarked\", which indicates the icon in the filled state." + ) + + bookmarksMenu.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // And the bookmark is found in the Bookmarks menu + app.menuItems[pageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The bookmark in the \"Bookmarks\" menu with the title of the test page didn't appear with the expected title in a reasonable timeframe." + ) + } + + func test_bookmarks_canBeAddedTo_withSettingsItemBookmarkThisPage() { + openSiteToBookmark(bookmarkingViaDialog: false, escapingDialog: false) + optionsButton.clickAfterExistenceTestSucceeds() + openBookmarksMenuItem.hoverAfterExistenceTestSucceeds() + bookmarkPageMenuItem.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // Check Add Bookmark dialog for existence but don't click on it + defaultBookmarkDialogButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog option button didn't appear with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + addressBarBookmarkButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The address bar bookmark button didn't appear with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + bookmarkDialogBookmarkFolderDropdown.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog's bookmark folder dropdown didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarkDialogBookmarkFolderDropdownValue = try? XCTUnwrap( + bookmarkDialogBookmarkFolderDropdown.value as? String, + "It wasn't possible to get the value of the \"Add bookmark\" dialog's bookmark folder dropdown as String" + ) + XCTAssertEqual( // Bookmark dialog must default to "Bookmarks" folder + bookmarkDialogBookmarkFolderDropdownValue, + "Bookmarks", + "The accessibility value of the \"Add bookmark\" dialog's bookmark folder dropdown must be \"Bookmarks\"." + ) + + let addressBarBookmarkButtonValue = try? XCTUnwrap( + addressBarBookmarkButton.value as? String, + "It wasn't possible to get the value of the address bar bookmark button as String" + ) + XCTAssertEqual( // The bookmark icon is already in a filled state and it isn't necessary to click the add button + addressBarBookmarkButtonValue, + "Bookmarked", + "The accessibility value of the address bar bookmark button must be \"Bookmarked\", which indicates the icon in the filled state." + ) + + bookmarksMenu.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // And the bookmark is found in the Bookmarks menu + app.menuItems[pageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The bookmark in the \"Bookmarks\" menu with the title of the test page didn't appear with the expected title in a reasonable timeframe." + ) + } + + func test_bookmarks_canBeAddedTo_byClickingBookmarksButtonInAddressBar() { + openSiteToBookmark(bookmarkingViaDialog: false, escapingDialog: false) + // In order to directly click the bookmark button in the address bar, we need to hover over something in the bar area + optionsButton.hoverAfterExistenceTestSucceeds() + addressBarBookmarkButton.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // Check Add Bookmark dialog for existence but don't click on it + defaultBookmarkDialogButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog option button didn't appear with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + bookmarkDialogBookmarkFolderDropdown.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog's bookmark folder dropdown didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarkDialogBookmarkFolderDropdownValue = try? XCTUnwrap( + bookmarkDialogBookmarkFolderDropdown.value as? String, + "It wasn't possible to get the value of the \"Add bookmark\" dialog's bookmark folder dropdown as String" + ) + + XCTAssertEqual( // Bookmark dialog must default to "Bookmarks" folder + bookmarkDialogBookmarkFolderDropdownValue, + "Bookmarks", + "The accessibility value of the \"Add bookmark\" dialog's bookmark folder dropdown must be \"Bookmarks\"." + ) + let addressBarBookmarkButtonValue = try? XCTUnwrap( + addressBarBookmarkButton.value as? String, + "It wasn't possible to get the value of the address bar bookmark button as String" + ) + XCTAssertEqual( // The bookmark icon is already in a filled state and it isn't necessary to click the add button + addressBarBookmarkButtonValue, + "Bookmarked", + "The accessibility value of the address bar bookmark button must be \"Bookmarked\", which indicates the icon in the filled state." + ) + + bookmarksMenu.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // And the bookmark is found in the Bookmarks menu + app.menuItems[pageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The bookmark in the \"Bookmarks\" menu with the title of the test page didn't appear with the expected title in a reasonable timeframe." + ) + } + + func test_favorites_canBeAddedTo_byClickingFavoriteThisPageMenuBarItem() { + openSiteToBookmark(bookmarkingViaDialog: false, escapingDialog: false) + bookmarksMenu.clickAfterExistenceTestSucceeds() + favoriteThisPageMenuItem.clickAfterExistenceTestSucceeds() + + XCTAssertTrue( // Check Add Bookmark dialog for existence but don't click on it + defaultBookmarkDialogButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog option button didn't appear with the expected title in a reasonable timeframe." + ) + let addressBarBookmarkButtonValue = try? XCTUnwrap( + addressBarBookmarkButton.value as? String, + "It wasn't possible to get the value of the address bar bookmark button as String" + ) + XCTAssertEqual( // The bookmark icon is already in a filled state and it isn't necessary to click the add button + addressBarBookmarkButtonValue, + "Bookmarked", + "The accessibility value of the address bar bookmark button must be \"Bookmarked\", which indicates the icon in the filled state." + ) + XCTAssertTrue( // Check Add Bookmark dialog for existence but don't click on it + defaultBookmarkDialogButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog option button didn't appear with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + XCTAssertEqual( // The favorite checkbox in the dialog is already checked + bookmarksDialogAddToFavoritesCheckboxValue, + true, + "The the value of the bookmarks dialog's add to favorites checkbox must be checked, which indicates that the item has been favorited." + ) + } + + func test_favorites_canBeAddedTo_byClickingAddFavoriteInAddBookmarkPopover() { + openSiteToBookmark(bookmarkingViaDialog: false, escapingDialog: false) + // In order to directly click the bookmark button in the address bar, we need to hover over something in the bar area + optionsButton.hoverAfterExistenceTestSucceeds() + + addressBarBookmarkButton.clickAfterExistenceTestSucceeds() + XCTAssertTrue( // Check Add Bookmark dialog for existence before adding to favorites + defaultBookmarkDialogButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Add bookmark\" dialog option button didn't appear with the expected title in a reasonable timeframe." + ) + + bookmarksDialogAddToFavoritesCheckbox.clickAfterExistenceTestSucceeds() + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + XCTAssertEqual( // The favorite checkbox in the dialog is already checked + bookmarksDialogAddToFavoritesCheckboxValue, + true, + "The the value of the bookmarks dialog's add to favorites checkbox must be checked, which indicates that the item has been favorited." + ) + } + + func test_favorites_canBeManuallyAddedTo_byClickingAddFavoriteInNewTabPage() throws { + toggleBookmarksBarShowFavoritesOn() + + favoriteGridAddFavoriteButton.clickAfterExistenceTestSucceeds() + let pageTitleForAddFavoriteDialog: String = try XCTUnwrap(pageTitle, "Couldn't unwrap page title") + let urlForAddFavoriteDialog = try XCTUnwrap(urlForBookmarksBar, "Couldn't unwrap page url") + app.typeText("\(pageTitleForAddFavoriteDialog)\t") + app.typeURL(urlForAddFavoriteDialog) + let newFavorite = app.otherElements.staticTexts[pageTitleForAddFavoriteDialog] + + XCTAssertTrue( + newFavorite.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The new favorite on the new tab page did not become available in a reasonable timeframe." + ) + } + + func test_favorites_canBeAddedToFromManageBookmarksView() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + bookmarksMenu.clickAfterExistenceTestSucceeds() + manageBookmarksMenuItem.clickAfterExistenceTestSucceeds() + bookmarkTableCellViewFavIconImageView.hoverAfterExistenceTestSucceeds() + bookmarkTableCellViewMenuButton.clickAfterExistenceTestSucceeds() + + contextualMenuAddBookmarkToFavoritesMenuItem.clickAfterExistenceTestSucceeds() + XCTAssertTrue( + bookmarksManagementAccessoryImageView.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Bookmarks accessory view favorites indicator didn't load with the expected title in a reasonable timeframe." + ) + let bookmarksManagementAccessoryImageViewValue = try? XCTUnwrap( + bookmarksManagementAccessoryImageView.value as? String, + "It wasn't possible to get the value of the bookmarks management accessory image view as String" + ) + + XCTAssertEqual( + bookmarksManagementAccessoryImageViewValue, + "Favorited", + "The accessibility value of the favorite accessory view on the bookmark management view must be \"Favorited\"." + ) + } + + func test_bookmarks_canBeViewedInBookmarkMenuItem() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + addressBarBookmarkButton.clickAfterExistenceTestSucceeds() + + bookmarksMenu.clickAfterExistenceTestSucceeds() + let bookmarkedItemInMenu = app.menuItems[pageTitle] + + XCTAssertTrue( + bookmarkedItemInMenu.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Bookmarked page couldn't be detected in the bookmarks menu in a reasonable timeframe." + ) + } + + func test_bookmarks_canBeViewedInAddressBarBookmarkDialog() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + XCTAssertTrue( + addressBarBookmarkButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Address bar bookmark button didn't load with the expected title in a reasonable timeframe." + ) + let addressBarBookmarkButtonValue = try? XCTUnwrap( + addressBarBookmarkButton.value as? String, + "It wasn't possible to get the value of the bookmarks management accessory image view as String" + ) + XCTAssertEqual( + addressBarBookmarkButtonValue, + "Bookmarked", + "The accessibility value of the Address Bar Bookmark Button must be \"Bookmarked\"." + ) + + addressBarBookmarkButton.click() + let bookMarkDialogBookmarkTitle = app.textFields[pageTitle] + + XCTAssertTrue( + bookMarkDialogBookmarkTitle.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The bookmarked url title wasn't found in the bookmark dialog in a bookmarked state in a reasonable timeframe." + ) + } + + func test_bookmarksTab_canBeViewedViaMenuItemManageBookmarks() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + bookmarksMenu.clickAfterExistenceTestSucceeds() + + manageBookmarksMenuItem.clickAfterExistenceTestSucceeds() + + XCTAssertTrue( + bookmarksTabPopup.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Bookmarks tab bookmarks popup didn't load with the expected title in a reasonable timeframe." + ) + } + + func test_favorites_appearWithTheCorrectIndicatorInBookmarksTab() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: false) + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + if bookmarksDialogAddToFavoritesCheckboxValue == false { + bookmarksDialogAddToFavoritesCheckbox.click() + } + app.typeKey(.escape, modifierFlags: []) // Exit dialog + + bookmarksMenu.clickAfterExistenceTestSucceeds() + manageBookmarksMenuItem.clickAfterExistenceTestSucceeds() + XCTAssertTrue( + bookmarksTabPopup.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Bookmarks tab bookmarks popup didn't load with the expected title in a reasonable timeframe." + ) + XCTAssertTrue( + bookmarksManagementAccessoryImageView.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Bookmarks accessory view favorites indicator didn't load with the expected title in a reasonable timeframe." + ) + let bookmarksManagementAccessoryImageViewValue = try? XCTUnwrap( + bookmarksManagementAccessoryImageView.value as? String, + "It wasn't possible to get the value of the bookmarks management accessory image view as String" + ) + + XCTAssertEqual( + bookmarksManagementAccessoryImageViewValue, + "Favorited", + "The accessibility value of the favorite accessory view on the bookmark management view must be \"Favorited\"." + ) + } + + func test_favorites_appearInNewTabFavoritesGrid() throws { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: false) + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + if bookmarksDialogAddToFavoritesCheckboxValue == false { + bookmarksDialogAddToFavoritesCheckbox.click() + } + app.typeKey(.escape, modifierFlags: []) // Exit dialog + + toggleBookmarksBarShowFavoritesOn() + let unwrappedPageTitle = try XCTUnwrap(pageTitle, "It wasn't possible to unwrap pageTitle") + let firstFavoriteInGridMatchingTitle = app.staticTexts["HomePage.Models.FavoriteModel.\(unwrappedPageTitle)"].firstMatch + + XCTAssertTrue( + firstFavoriteInGridMatchingTitle.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The favorited item in the grid did not become available in a reasonable timeframe." + ) + } + + func test_favorites_canBeRemovedFromAddressBarBookmarkDialog() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: false) + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + if bookmarksDialogAddToFavoritesCheckboxValue == false { + bookmarksDialogAddToFavoritesCheckbox.click() // Favorite the bookmark + } + app.typeKey(.escape, modifierFlags: []) // Exit dialog + + XCTAssertTrue( + addressBarBookmarkButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Address bar bookmark button didn't load with the expected title in a reasonable timeframe." + ) + let addressBarBookmarkButtonValue = try? XCTUnwrap( + addressBarBookmarkButton.value as? String, + "It wasn't possible to get the value of the bookmarks management accessory image view as String" + ) + XCTAssertEqual( + addressBarBookmarkButtonValue, + "Bookmarked", + "The accessibility value of the Address Bar Bookmark Button must be \"Bookmarked\"." + ) + addressBarBookmarkButton.click() + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarksDialogAddToFavoritesCheckboxNewValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + if bookmarksDialogAddToFavoritesCheckboxNewValue == true { + bookmarksDialogAddToFavoritesCheckbox.click() // Unfavorite the bookmark + } + let bookmarksDialogAddToFavoritesCheckboxLastValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + let addToFavoritesLabel = "Add to Favorites" + + XCTAssertEqual( + bookmarksDialogAddToFavoritesCheckboxLastValue, + false, + "The favorite checkbox in the add bookmark dialog must now be unchecked" + ) + XCTAssertEqual( + bookmarksDialogAddToFavoritesCheckbox.label, + addToFavoritesLabel, + "The label of the add to favorites checkbox must now be \"\(addToFavoritesLabel)\"" + ) + } + + func test_favorites_canBeRemovedFromManageBookmarks() { + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: false) + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + if bookmarksDialogAddToFavoritesCheckboxValue == false { + bookmarksDialogAddToFavoritesCheckbox.click() // Favorite the bookmark + } + app.typeKey(.escape, modifierFlags: []) // Exit dialog + + bookmarksMenu.clickAfterExistenceTestSucceeds() + manageBookmarksMenuItem.clickAfterExistenceTestSucceeds() + bookmarkTableCellViewFavIconImageView.hoverAfterExistenceTestSucceeds() + bookmarkTableCellViewMenuButton.clickAfterExistenceTestSucceeds() + contextualMenuRemoveBookmarkFromFavoritesMenuItem.clickAfterExistenceTestSucceeds() + + XCTAssertTrue( + bookmarksManagementAccessoryImageView.waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "Bookmarks accessory view favorites indicator didn't disappear from the view in a reasonable timeframe." + ) + } + + func test_favorites_canBeRemovedFromNewTabViaContextClick() throws { + toggleBookmarksBarShowFavoritesOn() + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: false) + XCTAssertTrue( + bookmarksDialogAddToFavoritesCheckbox.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The add to favorites checkbox in the add bookmark dialog didn't appear with the expected title in a reasonable timeframe." + ) + let bookmarksDialogAddToFavoritesCheckboxValue = try? XCTUnwrap( + bookmarksDialogAddToFavoritesCheckbox.value as? Bool, + "It wasn't possible to get the value of the bookmarks dialog's add to favorites checkbox as Bool" + ) + if bookmarksDialogAddToFavoritesCheckboxValue == false { + bookmarksDialogAddToFavoritesCheckbox.click() // Favorite the bookmark + } + app.typeKey(.escape, modifierFlags: []) // Exit dialog + app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close all windows + app.typeKey("n", modifierFlags: .command) // New window + + let unwrappedPageTitle = try XCTUnwrap(pageTitle, "It wasn't possible to unwrap pageTitle") + let firstFavoriteInGridMatchingTitle = app.staticTexts["HomePage.Models.FavoriteModel.\(unwrappedPageTitle)"].firstMatch + XCTAssertTrue( + firstFavoriteInGridMatchingTitle.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The favorited item in the grid did not become available in a reasonable timeframe." + ) + firstFavoriteInGridMatchingTitle.rightClick() + removeFavoritesContextMenuItem.clickAfterExistenceTestSucceeds() + + XCTAssertTrue( + firstFavoriteInGridMatchingTitle.waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "The favorited item in the grid did not disappear in a reasonable timeframe." + ) + } + + func test_bookmark_canBeRemovedViaAddressBarIconClick() { + toggleShowBookmarksBarAlwaysOn() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + + addressBarBookmarkButton.clickAfterExistenceTestSucceeds() + defaultBookmarkOtherButton.clickAfterExistenceTestSucceeds() + app.typeKey(.escape, modifierFlags: []) // Exit dialog + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + + XCTAssertTrue( + app.staticTexts[pageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "Since there is no bookmark of the page, and we show bookmarks in the bookmark bar, the title of the page should not appear in a new browser window anywhere." + ) + } + + func test_bookmark_canBeRemovedFromBookmarksTabViaHoverAndContextMenu() { + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + + bookmarksMenu.clickAfterExistenceTestSucceeds() + manageBookmarksMenuItem.clickAfterExistenceTestSucceeds() + bookmarkTableCellViewFavIconImageView.hoverAfterExistenceTestSucceeds() + bookmarkTableCellViewMenuButton.clickAfterExistenceTestSucceeds() + contextualMenuDeleteBookmarkMenuItem.clickAfterExistenceTestSucceeds() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + + XCTAssertTrue( + app.staticTexts[pageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "Since there is no bookmark of the page, and we show bookmarks in the bookmark bar, the title of the page should not appear in a new browser window anywhere." + ) + } + + func test_bookmark_canBeRemovedFromBookmarksBarViaRightClick() { +// This test uses coordinates (instead of accessibility IDs) to address the elements of the right click. As the writer of this test, I see this +// as a fragile test hook. However, I think it is preferable to making changes to the UI element it tests for this test alone. The reason is +// that the bookmark item on the bookmark bar isn't yet an accessibility-enabled UI element and doesn't appear to have a natural anchor point +// from which we can set its accessibility values without redesigning it. However, redesigning a road-tested UI element for a single test isn't a +// good idea, since the road-testing is also (valuable) testing and we don't want a single test to be the driver of a possible behavioral +// change in existing interface. +// +// My advice is to keep this as-is for now, with an awareness that it can fail if the coordinates of the items in the right-click menu change, +// or if the system where the testing is done has accessibility settings which change scaling. When the time comes to update this element, into +// SwiftUI, or into a general accessibility revision (for end-user accessibility rather than UI test accessibility), that will be the natural +// time to correct this test and give it accessibility ID access. Until then, I have added some hinting in the failure reason to explain why +// this test can fail while the app is working correctly. -Halle Winkler + + toggleShowBookmarksBarAlwaysOn() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + openSiteToBookmark(bookmarkingViaDialog: true, escapingDialog: true) + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + + XCTAssertTrue( + bookmarksBarCollectionView.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The bookmarks bar collection view failed to become available in a reasonable timeframe." + ) + let bookmarkBarBookmarkIcon = bookmarksBarCollectionView.images.firstMatch + XCTAssertTrue( + bookmarkBarBookmarkIcon.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The bookmarks bar bookmark icon failed to become available in a reasonable timeframe." + ) + let bookmarkBarBookmarkIconCoordinate = bookmarkBarBookmarkIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) + let deleteContextMenuItemCoordinate = bookmarkBarBookmarkIcon.coordinate(withNormalizedOffset: CGVector(dx: 0.9, dy: 9.0)) + bookmarkBarBookmarkIconCoordinate.rightClick() + deleteContextMenuItemCoordinate.click() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) + app.typeKey("n", modifierFlags: .command) + + XCTAssertTrue( + app.staticTexts[pageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "Since there is no bookmark of the page, and we show bookmarks in the bookmark bar, the title of the page should not appear in a new browser window anywhere. In this specific test, it is highly probable that the reason for a failure (when this area of the app appears to be working correctly) is the contextual menu being rearranged, since it has to address the menu elements by coordinate." + ) + } +} + +private extension BookmarksAndFavoritesTests { + /// Reset the bookmarks so we can rely on a single bookmark's existence + func resetBookmarks() { + app.typeKey("n", modifierFlags: [.command]) // Can't use debug menu without a window + XCTAssertTrue( + resetBookMarksMenuItem.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Reset bookmarks menu item didn't become available in a reasonable timeframe." + ) + resetBookMarksMenuItem.click() + } + + /// Make sure that we can reply on the bookmarks bar always appearing + func toggleShowBookmarksBarAlwaysOn() { + app.typeKey(",", modifierFlags: [.command]) // Open settings + + XCTAssertTrue( + settingsAppearanceButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The user settings appearance section button didn't become available in a reasonable timeframe." + ) + settingsAppearanceButton.click(forDuration: 0.5, thenDragTo: settingsAppearanceButton) + XCTAssertTrue( + showBookmarksBarPreferenceToggle.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The toggle for showing the bookmarks bar didn't become available in a reasonable timeframe." + ) + + let showBookmarksBarIsChecked = try? XCTUnwrap( + showBookmarksBarPreferenceToggle.value as? Bool, + "It wasn't possible to get the \"Show bookmarks bar\" value as a Bool" + ) + if showBookmarksBarIsChecked == false { + showBookmarksBarPreferenceToggle.click() + } + XCTAssertTrue( + showBookmarksBarPopup.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Show Bookmarks Bar\" popup button didn't become available in a reasonable timeframe." + ) + showBookmarksBarPopup.click() + XCTAssertTrue( + showBookmarksBarAlways.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The \"Show Bookmarks Bar Always\" button didn't become available in a reasonable timeframe." + ) + showBookmarksBarAlways.click() + } + + /// Make sure that appearance tab has been used to set "show favorites" to true + func toggleBookmarksBarShowFavoritesOn() { + app.typeKey(",", modifierFlags: [.command]) // Open settings + + XCTAssertTrue( + settingsAppearanceButton.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The user settings appearance section button didn't become available in a reasonable timeframe." + ) + settingsAppearanceButton.click(forDuration: 0.5, thenDragTo: settingsAppearanceButton) + + XCTAssertTrue( + showFavoritesPreferenceToggle.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The user settings appearance section show favorites toggle didn't become available in a reasonable timeframe." + ) + let showFavoritesPreferenceToggleIsChecked = showFavoritesPreferenceToggle.value as? Bool + if showFavoritesPreferenceToggleIsChecked == false { // If untoggled, + showFavoritesPreferenceToggle.click() // Toggle "show favorites" + } + app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close settings and everything else + app.typeKey("n", modifierFlags: .command) // New window + } + + /// Open the initial site to be bookmarked, bookmarking it and/or escaping out of the dialog only if needed + /// - Parameter bookmarkingViaDialog: open bookmark dialog, adding bookmark + /// - Parameter escapingDialog: `esc` key to leave dialog + func openSiteToBookmark(bookmarkingViaDialog: Bool, escapingDialog: Bool) { + XCTAssertTrue( + addressBarTextField.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The address bar text field didn't become available in a reasonable timeframe." + ) + addressBarTextField.typeURL(urlForBookmarksBar) + XCTAssertTrue( + app.windows.webViews[pageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Visited site didn't load with the expected title in a reasonable timeframe." + ) + if bookmarkingViaDialog { + app.typeKey("d", modifierFlags: [.command]) // Add bookmark + if escapingDialog { + app.typeKey(.escape, modifierFlags: []) // Exit dialog + } + } + } +} diff --git a/UITests/BookmarksBarTests.swift b/UITests/BookmarksBarTests.swift index 55eecbe52b..eacea0c9aa 100644 --- a/UITests/BookmarksBarTests.swift +++ b/UITests/BookmarksBarTests.swift @@ -55,7 +55,6 @@ class BookmarksBarTests: XCTestCase { resetBookmarksAndAddOneBookmark() app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close windows openSettingsAndSetShowBookmarksBarToUnchecked() - settingsWindow = app.windows.containing(.checkBox, identifier: "Preferences.AppearanceView.showBookmarksBarPreferenceToggle").firstMatch openSecondWindowAndVisitSite() siteWindow = app.windows.containing(.webView, identifier: pageTitle).firstMatch } diff --git a/UITests/Common/UITests.swift b/UITests/Common/UITests.swift index 40f28936f9..438cf5be4d 100644 --- a/UITests/Common/UITests.swift +++ b/UITests/Common/UITests.swift @@ -24,7 +24,7 @@ enum UITests { /// Timeout constants for different test requirements enum Timeouts { /// Mostly, we use timeouts to wait for element existence. This is about 3x longer than needed, for CI resilience - static let elementExistence: Double = 2.5 + static let elementExistence: Double = 5.0 /// The fire animation time has environmental dependencies, so we want to wait for completion so we don't try to type into it static let fireAnimation: Double = 30.0 } diff --git a/UITests/Common/XCUIElementExtension.swift b/UITests/Common/XCUIElementExtension.swift index 7e72c41a02..1e0d0ba991 100644 --- a/UITests/Common/XCUIElementExtension.swift +++ b/UITests/Common/XCUIElementExtension.swift @@ -62,4 +62,20 @@ extension XCUIElement { self.typeText("\r") } } + + func clickAfterExistenceTestSucceeds() { + XCTAssertTrue( + self.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "\(self.debugDescription) didn't load with the expected title in a reasonable timeframe." + ) + self.click() + } + + func hoverAfterExistenceTestSucceeds() { + XCTAssertTrue( + self.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "\(self.debugDescription) didn't load with the expected title in a reasonable timeframe." + ) + self.hover() + } } From 0d7c9de8780696937690d0841d0a7fd73a88322c Mon Sep 17 00:00:00 2001 From: Tom Strba <57389842+tomasstrba@users.noreply.github.com> Date: Tue, 9 Apr 2024 15:31:56 +0200 Subject: [PATCH 12/40] New daily pixel for history save failures (#2533) Task/Issue URL: https://app.asana.com/0/0/1206920829163329/f **Description**: Adding new daily pixel for history save failures to better understand how many devices are affected by a potential issue --- DuckDuckGo/History/Services/EncryptedHistoryStore.swift | 3 +++ DuckDuckGo/Statistics/PixelEvent.swift | 3 +++ DuckDuckGo/Statistics/PixelParameters.swift | 1 + 3 files changed, 7 insertions(+) diff --git a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift index 361ab26a81..36838c0539 100644 --- a/DuckDuckGo/History/Services/EncryptedHistoryStore.swift +++ b/DuckDuckGo/History/Services/EncryptedHistoryStore.swift @@ -175,6 +175,7 @@ final class EncryptedHistoryStore: HistoryStoring { fetchedObjects = try self.context.fetch(fetchRequest) } catch { Pixel.fire(.debug(event: .historySaveFailed, error: error)) + Pixel.fire(.debug(event: .historySaveFailedDaily, error: error), limitTo: .dailyFirst) promise(.failure(error)) return } @@ -203,6 +204,7 @@ final class EncryptedHistoryStore: HistoryStoring { switch insertionResult { case .failure(let error): Pixel.fire(.debug(event: .historySaveFailed, error: error)) + Pixel.fire(.debug(event: .historySaveFailedDaily, error: error), limitTo: .dailyFirst) context.reset() promise(.failure(error)) case .success(let visitMOs): @@ -210,6 +212,7 @@ final class EncryptedHistoryStore: HistoryStoring { try self.context.save() } catch { Pixel.fire(.debug(event: .historySaveFailed, error: error)) + Pixel.fire(.debug(event: .historySaveFailedDaily, error: error), limitTo: .dailyFirst) context.reset() promise(.failure(HistoryStoreError.savingFailed)) return diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index 9f789182da..902e5b0b5a 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -326,6 +326,7 @@ extension Pixel { case historyCleanEntriesFailed case historyCleanVisitsFailed case historySaveFailed + case historySaveFailedDaily case historyInsertVisitFailed case historyRemoveVisitsFailed @@ -816,6 +817,8 @@ extension Pixel.Event.Debug { return "history_clean_visits_failed" case .historySaveFailed: return "history_save_failed" + case .historySaveFailedDaily: + return "history_save_failed_daily" case .historyInsertVisitFailed: return "history_insert_visit_failed" case .historyRemoveVisitsFailed: diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index 3b7d89b5c4..2fad773b42 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -256,6 +256,7 @@ extension Pixel.Event.Debug { .historyCleanEntriesFailed, .historyCleanVisitsFailed, .historySaveFailed, + .historySaveFailedDaily, .historyInsertVisitFailed, .historyRemoveVisitsFailed, .emailAutofillKeychainError, From ebb09c340f47ae13a3bd849caec8b769050d7e56 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:40:07 -0400 Subject: [PATCH 13/40] Change DAU pixel for VPN to weekly (#2552) Task/Issue URL: https://app.asana.com/0/0/1206685546588311/f **Description**: Reported `cohort` now uses the week number. --- .../MacPacketTunnelProvider.swift | 2 +- .../PixelKit/PixelKit+Parameters.swift | 2 + .../PixelKit/Sources/PixelKit/PixelKit.swift | 32 ++++--- .../Tests/PixelKitTests/PixelKitTests.swift | 96 ++++++++++++++++--- 4 files changed, 104 insertions(+), 28 deletions(-) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index c9a8819faa..0689316a76 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -151,7 +151,7 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { PixelKit.fire( NetworkProtectionPixelEvent.networkProtectionActiveUser, frequency: .dailyOnly, - withAdditionalParameters: ["cohort": PixelKit.dateString(for: defaults.vpnFirstEnabled)], + withAdditionalParameters: [PixelKit.Parameters.vpnCohort: PixelKit.cohort(from: defaults.vpnFirstEnabled)], includeAppVersionParameter: true) case .reportConnectionAttempt(attempt: let attempt): switch attempt { diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift index 777329156a..b692271709 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift @@ -66,6 +66,8 @@ public extension PixelKit { public static let vpnBreakageMetadata = "breakageMetadata" public static let reason = "reason" + + public static let vpnCohort = "cohort" } enum Values { diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index eec1e1332c..f55ec17ba4 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -72,13 +72,7 @@ public final class PixelKit { return calendar }() - private var dateFormatter: DateFormatter = { - let dateFormatter = DateFormatter() - dateFormatter.calendar = defaultDailyPixelCalendar - dateFormatter.timeZone = defaultDailyPixelCalendar.timeZone - dateFormatter.dateFormat = "yyyy-MM-dd" - return dateFormatter - }() + private static let weeksToCoalesceCohort = 6 private let dateGenerator: () -> Date @@ -99,6 +93,8 @@ public final class PixelKit { source: String? = nil, defaultHeaders: [String: String], log: OSLog, + dailyPixelCalendar: Calendar? = nil, + dateGenerator: @escaping () -> Date = Date.init, defaults: UserDefaults, fireRequest: @escaping FireRequest) { shared = PixelKit(dryRun: dryRun, @@ -106,6 +102,8 @@ public final class PixelKit { source: source, defaultHeaders: defaultHeaders, log: log, + dailyPixelCalendar: dailyPixelCalendar, + dateGenerator: dateGenerator, defaults: defaults, fireRequest: fireRequest) } @@ -318,13 +316,23 @@ public final class PixelKit { onComplete: onComplete) } - private func dateString(for date: Date?) -> String? { - guard let date else { return nil } - return dateFormatter.string(from: date) + private func cohort(from cohortLocalDate: Date?, dateGenerator: () -> Date = Date.init) -> String? { + guard let cohortLocalDate, + let baseDate = pixelCalendar.date(from: .init(year: 2023, month: 1, day: 1)), + let weeksSinceCohortAssigned = pixelCalendar.dateComponents([.weekOfYear], from: cohortLocalDate, to: dateGenerator()).weekOfYear, + let assignedCohort = pixelCalendar.dateComponents([.weekOfYear], from: baseDate, to: cohortLocalDate).weekOfYear else { + return nil + } + + if weeksSinceCohortAssigned > Self.weeksToCoalesceCohort { + return "" + } else { + return "week-" + String(assignedCohort + 1) + } } - public static func dateString(for date: Date?) -> String { - Self.shared?.dateString(for: date) ?? "" + public static func cohort(from cohortLocalDate: Date?, dateGenerator: () -> Date = Date.init) -> String { + Self.shared?.cohort(from: cohortLocalDate, dateGenerator: dateGenerator) ?? "" } public static func pixelLastFireDate(event: Event) -> Date? { diff --git a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift index b9576d6a6a..3ce7447aae 100644 --- a/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift +++ b/LocalPackages/PixelKit/Tests/PixelKitTests/PixelKitTests.swift @@ -258,17 +258,18 @@ final class PixelKitTests: XCTestCase { // Run test pixelKit.fire(event, frequency: .dailyOnly) // Fired - timeMachine.travel(by: 60 * 60 * 2) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped (2 hours since last fire) + timeMachine.travel(by: .hour, value: 2) + pixelKit.fire(event, frequency: .dailyOnly) // Skipped - timeMachine.travel(by: 60 * 60 * 24 + 1000) - pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours + 1000 seconds since last fire) + timeMachine.travel(by: .day, value: 1) + timeMachine.travel(by: .hour, value: 2) + pixelKit.fire(event, frequency: .dailyOnly) // Fired - timeMachine.travel(by: 60 * 60 * 10) - pixelKit.fire(event, frequency: .dailyOnly) // Skipped (10 hours since last fire) + timeMachine.travel(by: .hour, value: 10) + pixelKit.fire(event, frequency: .dailyOnly) // Skipped - timeMachine.travel(by: 60 * 60 * 14) - pixelKit.fire(event, frequency: .dailyOnly) // Fired (24 hours since last fire) + timeMachine.travel(by: .day, value: 1) + pixelKit.fire(event, frequency: .dailyOnly) // Fired // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) @@ -303,28 +304,93 @@ final class PixelKitTests: XCTestCase { // Run test pixelKit.fire(event, frequency: .justOnce) // Fired - timeMachine.travel(by: 60 * 60 * 2) + timeMachine.travel(by: .hour, value: 2) pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) - timeMachine.travel(by: 60 * 60 * 24 + 1000) + timeMachine.travel(by: .day, value: 1) + timeMachine.travel(by: .hour, value: 2) pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) - timeMachine.travel(by: 60 * 60 * 10) + timeMachine.travel(by: .hour, value: 10) pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) - timeMachine.travel(by: 60 * 60 * 14) + timeMachine.travel(by: .day, value: 1) pixelKit.fire(event, frequency: .justOnce) // Skipped (already fired) // Wait for expectations to be fulfilled wait(for: [fireCallbackCalled], timeout: 0.5) } + + func testVPNCohort() { + XCTAssertEqual(PixelKit.cohort(from: nil), "") + assertCohortEqual(.init(year: 2023, month: 1, day: 1), reportAs: "week-1") + assertCohortEqual(.init(year: 2024, month: 2, day: 24), reportAs: "week-60") + } + + private func assertCohortEqual(_ cohort: DateComponents, reportAs reportedCohort: String) { + var calendar = Calendar.current + calendar.timeZone = TimeZone(secondsFromGMT: 0)! + calendar.locale = Locale(identifier: "en_US_POSIX") + + let cohort = calendar.date(from: cohort) + let timeMachine = TimeMachine(calendar: calendar, date: cohort) + + PixelKit.setUp(appVersion: "test", + defaultHeaders: [:], + log: .disabled, + dailyPixelCalendar: calendar, + dateGenerator: timeMachine.now, + defaults: userDefaults()) { _, _, _, _, _, _ in } + + // 1st week + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 2nd week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 3rd week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 4th week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 5th week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 6th week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 7th week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), reportedCohort) + + // 8th week + timeMachine.travel(by: .weekOfYear, value: 1) + XCTAssertEqual(PixelKit.cohort(from: cohort, dateGenerator: timeMachine.now), "") + } } private class TimeMachine { - private var date = Date(timeIntervalSince1970: 0) + private var date: Date + private let calendar: Calendar + + init(calendar: Calendar? = nil, date: Date? = nil) { + self.calendar = calendar ?? { + var calendar = Calendar.current + calendar.timeZone = TimeZone(secondsFromGMT: 0)! + calendar.locale = Locale(identifier: "en_US_POSIX") + return calendar + }() + self.date = date ?? .init(timeIntervalSince1970: 0) + } - func travel(by timeInterval: TimeInterval) { - date = date.addingTimeInterval(timeInterval) + func travel(by component: Calendar.Component, value: Int) { + date = calendar.date(byAdding: component, value: value, to: now())! } func now() -> Date { From 4b9fa1a3e5c80234e6ac4a4be0e43d7415a05d70 Mon Sep 17 00:00:00 2001 From: Dax Mobile <44842493+daxmobile@users.noreply.github.com> Date: Tue, 9 Apr 2024 18:08:37 +0200 Subject: [PATCH 14/40] Update BSK with autofill 11.0.1 (#2588) Task/Issue URL: https://app.asana.com/0/1207038392239720/1207038392239720 Autofill Release: https://github.com/duckduckgo/duckduckgo-autofill/releases/tag/11.0.1 BSK PR: duckduckgo/BrowserServicesKit#769 Description Updates Autofill to version 11.0.1. --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index dfa32b6122..17154ba5d0 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14452,7 +14452,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 133.0.0; + version = 133.0.1; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 0e02888fb7..12fabf86b4 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" : "c0b0cb55e7ac2f69d10452e1a5c06713155d798e", - "version" : "133.0.0" + "revision" : "39d74829150a9ecffea2f503c01851e54eda8ad1", + "version" : "133.0.1" } }, { @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", "state" : { - "revision" : "6493e296934bf09277c03df45f11f4619711cb24", - "version" : "10.2.0" + "revision" : "6053999d6af384a716ab0ce7205dbab5d70ed1b3", + "version" : "11.0.1" } }, { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 46c094664b..9418e01778 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.1"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index d0ea0235c3..4e62507c16 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), .package(path: "../LoginItems"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 556d4f308e..1c064eb962 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.1"), .package(path: "../SwiftUIExtensions") ], targets: [ From 532ec92fc6bd42a85007b5868640601713e449cd Mon Sep 17 00:00:00 2001 From: Halle <378795+Halle@users.noreply.github.com> Date: Tue, 9 Apr 2024 09:13:06 -0700 Subject: [PATCH 15/40] Adds a series of UI tests for State Restoration Task/Issue URL: https://app.asana.com/0/1199230911884351/1205717021705374/f Tech Design URL: CC: **Description**: Adds a series of UI tests for State Restoration **Steps to test this PR**: 1. Open the scheme **UI Tests** 2. Navigate to the test pane 3. Run `StateRestorationTests` **Note**: If this builds a `review` build that is treated as a first run on your system during every test (for instance, asking you where to put the application), please **build and run** the scheme once instead of running its tests, and go through the new app install first run steps once before running the tests, and answer any first install questions. **UI Tests general guidelines for everyone**: * It (unfortunately!) isn't possible to multitask while running UI tests on your local machine, because your mousing/interface usage will change or intercept screen interactions that the tests depend on. * Much as we all want UI testing to work on multi-monitor setups, we have noticed specific focus issues related to waiting for focus across two monitors simultaneously, so we have decided to test the tests on a single monitor. * We are currently testing with English as the app destination language. * If you experience test failures in your local environment, please always send the `xcresult` bundle so it is possible to view what happened differently on your machine, even if it seems like the same failure as a previous failure (it may be subtly different). Please give its screenshots or screen recording a look first before sending, just to rule out one-off failure causes like an unrelated app unexpectedly throwing up an update helper (or something similar) in front of where `XCUITest` is waiting for a UI element to exist. * Since the `xcresult` bundle will include a screen recording or screenshots, for your privacy, please consider things like your chats, calls, and open documents that you don't want to send to a contractor when you are running the tests. Thank you very much for your help! --- DuckDuckGo.xcodeproj/project.pbxproj | 4 + .../View/PreferencesGeneralView.swift | 8 +- UITests/StateRestorationTests.swift | 134 ++++++++++++++++++ 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 UITests/StateRestorationTests.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 17154ba5d0..d7da5c939e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3303,6 +3303,7 @@ EE7295ED2A545C0A008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295EC2A545C0A008C0991 /* NetworkProtection */; }; EE7295EF2A545C12008C0991 /* NetworkProtection in Frameworks */ = {isa = PBXBuildFile; productRef = EE7295EE2A545C12008C0991 /* NetworkProtection */; }; EE7F74912BB5D76600CD9456 /* BookmarksBarTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE7F74902BB5D76600CD9456 /* BookmarksBarTests.swift */; }; + EE9D81C32BC57A3700338BE3 /* StateRestorationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE9D81C22BC57A3700338BE3 /* StateRestorationTests.swift */; }; EEA3EEB12B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3EEB02B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift */; }; EEA3EEB32B24EC0600E8333A /* VPNLocationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEA3EEB22B24EC0600E8333A /* VPNLocationViewModel.swift */; }; EEAD7A7C2A1D3E20002A24E7 /* AppLauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */; }; @@ -4777,6 +4778,7 @@ EE66418B2B9B1981005BCD17 /* NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NetworkProtectionTokenStore+SubscriptionTokenKeychainStorage.swift"; sourceTree = ""; }; EE66666E2B56EDE4001D898D /* VPNLocationsHostingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VPNLocationsHostingViewController.swift; sourceTree = ""; }; EE7F74902BB5D76600CD9456 /* BookmarksBarTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BookmarksBarTests.swift; sourceTree = ""; }; + EE9D81C22BC57A3700338BE3 /* StateRestorationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateRestorationTests.swift; sourceTree = ""; }; EEA3EEB02B24EBD000E8333A /* NetworkProtectionVPNCountryLabelsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NetworkProtectionVPNCountryLabelsModel.swift; sourceTree = ""; }; EEA3EEB22B24EC0600E8333A /* VPNLocationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VPNLocationViewModel.swift; sourceTree = ""; }; EEAD7A6E2A1D3E1F002A24E7 /* AppLauncher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppLauncher.swift; sourceTree = ""; }; @@ -6562,6 +6564,7 @@ EE7F74902BB5D76600CD9456 /* BookmarksBarTests.swift */, EE02D41B2BB460A600DBE6B3 /* BrowsingHistoryTests.swift */, EE0429DF2BA31D2F009EB20F /* FindInPageTests.swift */, + EE9D81C22BC57A3700338BE3 /* StateRestorationTests.swift */, 7B4CE8E626F02134009134B1 /* TabBarTests.swift */, ); path = UITests; @@ -12282,6 +12285,7 @@ EE02D41A2BB4609900DBE6B3 /* UITests.swift in Sources */, EE0429E02BA31D2F009EB20F /* FindInPageTests.swift in Sources */, EE02D4212BB460FE00DBE6B3 /* StringExtension.swift in Sources */, + EE9D81C32BC57A3700338BE3 /* StateRestorationTests.swift in Sources */, EE54F7B32BBFEA49006218DB /* BookmarksAndFavoritesTests.swift in Sources */, EE02D4222BB4611A00DBE6B3 /* TestsURLExtension.swift in Sources */, 7B4CE8E726F02135009134B1 /* TabBarTests.swift in Sources */, diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 591961ee75..521893ba2e 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -39,11 +39,13 @@ extension Preferences { PreferencePaneSubSection { Picker(selection: $startupModel.restorePreviousSession, content: { Text(UserText.showHomePage).tag(false) - .padding(.bottom, 4) + .padding(.bottom, 4).accessibilityIdentifier("PreferencesGeneralView.stateRestorePicker.openANewWindow") Text(UserText.reopenAllWindowsFromLastSession).tag(true) + .accessibilityIdentifier("PreferencesGeneralView.stateRestorePicker.reopenAllWindowsFromLastSession") }, label: {}) - .pickerStyle(.radioGroup) - .offset(x: PreferencesViews.Const.pickerHorizontalOffset) + .pickerStyle(.radioGroup) + .offset(x: PreferencesViews.Const.pickerHorizontalOffset) + .accessibilityIdentifier("PreferencesGeneralView.stateRestorePicker") } } diff --git a/UITests/StateRestorationTests.swift b/UITests/StateRestorationTests.swift new file mode 100644 index 0000000000..32f86a0d2e --- /dev/null +++ b/UITests/StateRestorationTests.swift @@ -0,0 +1,134 @@ +// +// StateRestorationTests.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import XCTest + +class StateRestorationTests: XCTestCase { + private var app: XCUIApplication! + private var firstPageTitle: String! + private var secondPageTitle: String! + private var firstURLForBookmarksBar: URL! + private var secondURLForBookmarksBar: URL! + private let titleStringLength = 12 + private var addressBarTextField: XCUIElement! + private var settingsGeneralButton: XCUIElement! + private var openANewWindowPreference: XCUIElement! + private var reopenAllWindowsFromLastSessionPreference: XCUIElement! + + override func setUpWithError() throws { + continueAfterFailure = false + app = XCUIApplication() + app.launchEnvironment["UITEST_MODE"] = "1" + firstPageTitle = UITests.randomPageTitle(length: titleStringLength) + secondPageTitle = UITests.randomPageTitle(length: titleStringLength) + firstURLForBookmarksBar = UITests.simpleServedPage(titled: firstPageTitle) + secondURLForBookmarksBar = UITests.simpleServedPage(titled: secondPageTitle) + addressBarTextField = app.windows.textFields["AddressBarViewController.addressBarTextField"] + settingsGeneralButton = app.buttons["PreferencesSidebar.generalButton"] + openANewWindowPreference = app.radioButtons["PreferencesGeneralView.stateRestorePicker.openANewWindow"] + reopenAllWindowsFromLastSessionPreference = app.radioButtons["PreferencesGeneralView.stateRestorePicker.reopenAllWindowsFromLastSession"] + + app.launch() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Let's enforce a single window + app.typeKey("n", modifierFlags: .command) + } + + override func tearDownWithError() throws { + app.terminate() + } + + func test_tabStateAtRelaunch_shouldContainTwoSitesVisitedInPreviousSession_whenReopenAllWindowsFromLastSessionIsSet() { + app.typeKey(",", modifierFlags: [.command]) // Open settings + settingsGeneralButton.click(forDuration: 0.5, thenDragTo: settingsGeneralButton) + reopenAllWindowsFromLastSessionPreference.clickAfterExistenceTestSucceeds() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close windows + app.typeKey("n", modifierFlags: .command) + XCTAssertTrue( + addressBarTextField.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The address bar text field didn't become available in a reasonable timeframe." + ) + addressBarTextField.typeURL(firstURLForBookmarksBar) + XCTAssertTrue( + app.windows.webViews[firstPageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Site didn't load with the expected title in a reasonable timeframe." + ) + app.typeKey("t", modifierFlags: [.command]) + app.typeURL(secondURLForBookmarksBar) + XCTAssertTrue( + app.windows.webViews[secondPageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Site didn't load with the expected title in a reasonable timeframe." + ) + + app.terminate() + app.launch() + + XCTAssertTrue( + app.windows.webViews[secondPageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Second visited site wasn't found in a webview with the expected title in a reasonable timeframe." + ) + app.typeKey("w", modifierFlags: [.command]) + XCTAssertTrue( + app.windows.webViews[firstPageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "First visited site wasn't found in a webview with the expected title in a reasonable timeframe." + ) + } + + func test_tabStateAtRelaunch_shouldContainNoSitesVisitedInPreviousSession_whenReopenAllWindowsFromLastSessionIsUnset() { + app.typeKey(",", modifierFlags: [.command]) // Open settings + settingsGeneralButton.click(forDuration: 0.5, thenDragTo: settingsGeneralButton) + openANewWindowPreference.clickAfterExistenceTestSucceeds() + app.typeKey("w", modifierFlags: [.command, .option, .shift]) // Close windows + app.typeKey("n", modifierFlags: .command) + XCTAssertTrue( + addressBarTextField.waitForExistence(timeout: UITests.Timeouts.elementExistence), + "The address bar text field didn't become available in a reasonable timeframe." + ) + addressBarTextField.typeURL(firstURLForBookmarksBar) + XCTAssertTrue( + app.windows.webViews[firstPageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Site didn't load with the expected title in a reasonable timeframe." + ) + app.typeKey("t", modifierFlags: [.command]) + app.typeURL(secondURLForBookmarksBar) + XCTAssertTrue( + app.windows.webViews[secondPageTitle].waitForExistence(timeout: UITests.Timeouts.elementExistence), + "Site didn't load with the expected title in a reasonable timeframe." + ) + + app.terminate() + app.launch() + + XCTAssertTrue( + app.windows.webViews[secondPageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "Second visited site from previous session should not be in any webview." + ) + XCTAssertTrue( + app.windows.webViews[firstPageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "First visited site from previous session should not be in any webview." + ) + app.typeKey("w", modifierFlags: [.command]) + XCTAssertTrue( + app.windows.webViews[firstPageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "First visited site from previous session should not be in any webview." + ) + XCTAssertTrue( + app.windows.webViews[secondPageTitle].waitForNonExistence(timeout: UITests.Timeouts.elementExistence), + "Second visited site from previous session should not be in any webview." + ) + } +} From 04e56c9ecf961163b269c9db65399a2012685095 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 9 Apr 2024 18:01:43 +0000 Subject: [PATCH 16/40] Bump version to 1.82.1 (155) --- Configuration/BuildNumber.xcconfig | 2 +- Configuration/Version.xcconfig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 92feb2ccd4..8d555e6249 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 152 +CURRENT_PROJECT_VERSION = 155 diff --git a/Configuration/Version.xcconfig b/Configuration/Version.xcconfig index 4aebe00d0f..0868ba0c18 100644 --- a/Configuration/Version.xcconfig +++ b/Configuration/Version.xcconfig @@ -1 +1 @@ -MARKETING_VERSION = 1.82.0 +MARKETING_VERSION = 1.82.1 From fb1e132c664f973e1c0acd88e6c609c3c6b00b14 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Tue, 9 Apr 2024 20:43:37 +0200 Subject: [PATCH 17/40] Hotfixing an issue with VPN visibility for certain waitlist users (#2591) Task/Issue URL: https://app.asana.com/0/0/1207040067636855/f Description Fixes an issue where some users were seeing waitlist UI when the subscription was avilable. --- .../View/NavigationBarViewController.swift | 30 ++++++++++++------- .../NetworkProtectionFeatureDisabler.swift | 4 ++- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 2148a66473..fbf3cc1ae8 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -338,18 +338,26 @@ final class NavigationBarViewController: NSViewController { } #endif - // 1. If the user is on the waitlist but hasn't been invited or accepted terms and conditions, show the waitlist screen. - // 2. If the user has no waitlist state but has an auth token, show the NetP popover. - // 3. If the user has no state of any kind, show the waitlist screen. - - if NetworkProtectionWaitlist().shouldShowWaitlistViewController { - NetworkProtectionWaitlistViewControllerPresenter.show() - DailyPixel.fire(pixel: .networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) - } else if NetworkProtectionKeychainTokenStore().isFeatureActivated { - popovers.toggleNetworkProtectionPopover(usingView: networkProtectionButton, withDelegate: networkProtectionButtonModel) + // Note: the following code is quite contrived but we're aiming to hotfix issues without mixing subscription and + // waitlist logic. This should be cleaned up once waitlist can safely be removed. + + if DefaultSubscriptionFeatureAvailability().isFeatureAvailable { + if NetworkProtectionKeychainTokenStore().isFeatureActivated { + popovers.toggleNetworkProtectionPopover(usingView: networkProtectionButton, withDelegate: networkProtectionButtonModel) + } } else { - NetworkProtectionWaitlistViewControllerPresenter.show() - DailyPixel.fire(pixel: .networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) + // 1. If the user is on the waitlist but hasn't been invited or accepted terms and conditions, show the waitlist screen. + // 2. If the user has no waitlist state but has an auth token, show the NetP popover. + // 3. If the user has no state of any kind, show the waitlist screen. + + if NetworkProtectionWaitlist().shouldShowWaitlistViewController { + NetworkProtectionWaitlistViewControllerPresenter.show() + DailyPixel.fire(pixel: .networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) + } else if NetworkProtectionKeychainTokenStore().isFeatureActivated { + popovers.toggleNetworkProtectionPopover(usingView: networkProtectionButton, withDelegate: networkProtectionButtonModel) + } else { + NetworkProtectionWaitlistViewControllerPresenter.show() + } } } #endif diff --git a/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift b/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift index ba12c9cdd2..d7cc337991 100644 --- a/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift +++ b/DuckDuckGo/Waitlist/NetworkProtectionFeatureDisabler.swift @@ -76,6 +76,9 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling @MainActor @discardableResult func disable(keepAuthToken: Bool, uninstallSystemExtension: Bool) async -> Bool { + // We can do this optimistically as it has little if any impact. + unpinNetworkProtection() + // To disable NetP we need the login item to be running // This should be fine though as we'll disable them further down below guard canUninstall(includingSystemExtension: uninstallSystemExtension) else { @@ -85,7 +88,6 @@ final class NetworkProtectionFeatureDisabler: NetworkProtectionFeatureDisabling isDisabling = true defer { - unpinNetworkProtection() resetUserDefaults(uninstallSystemExtension: uninstallSystemExtension) } From e562f1166d12ea4d095b45ae28f210a2fc7f215f Mon Sep 17 00:00:00 2001 From: Dominik Kapusta Date: Tue, 9 Apr 2024 22:32:31 +0200 Subject: [PATCH 18/40] Remove non-existent networkProtectionWaitlistIntroDisplayed pixel --- DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 55eb55aa20..0274af6b08 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -323,7 +323,6 @@ final class NavigationBarViewController: NSViewController { if NetworkProtectionWaitlist().shouldShowWaitlistViewController { NetworkProtectionWaitlistViewControllerPresenter.show() - DailyPixel.fire(pixel: .networkProtectionWaitlistIntroDisplayed, frequency: .dailyAndCount) } else if NetworkProtectionKeychainTokenStore().isFeatureActivated { popovers.toggleNetworkProtectionPopover(usingView: networkProtectionButton, withDelegate: networkProtectionButtonModel) } else { From faadae581ef25e7e2734b1a45da1abc7ccc36282 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Tue, 9 Apr 2024 20:42:26 +0000 Subject: [PATCH 19/40] Bump version to 1.83.0 (156) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 8d555e6249..1896f3713a 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 155 +CURRENT_PROJECT_VERSION = 156 From 22484b7755599b29e400c0cd9337179a6586fd35 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Tue, 9 Apr 2024 21:48:02 -0700 Subject: [PATCH 20/40] Hide the `DuckDuckGo on Other Platforms` section on the App Store (#2592) Task/Issue URL: https://app.asana.com/0/1199230911884351/1207033225544215/f Description: This PR updates the "DuckDuckGo on Other Platforms" section of the Settings page to only show on Sparkle builds. This is done because Apple forbids references to other platforms on the App Store. --- DuckDuckGo/Preferences/Model/PreferencesSection.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 161ffb3440..edf564d458 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -49,7 +49,12 @@ struct PreferencesSection: Hashable, Identifiable { return panes }() +#if APPSTORE + // App Store guidelines don't allow references to other platforms, so the Mac App Store build omits the otherPlatforms section. + let otherPanes: [PreferencePaneIdentifier] = [.about] +#else let otherPanes: [PreferencePaneIdentifier] = [.about, .otherPlatforms] +#endif var sections: [PreferencesSection] = [ .init(id: .privacyProtections, panes: privacyPanes), From 86f799f8e0a2bf4babeb693490357649c4eb2257 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Wed, 10 Apr 2024 05:12:08 +0000 Subject: [PATCH 21/40] Bump version to 1.83.0 (157) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 1896f3713a..eb40c52354 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 156 +CURRENT_PROJECT_VERSION = 157 From 39922beda185a96d7b1a2b4fcb37ecdb80cf0d6f Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 10:36:38 +0500 Subject: [PATCH 22/40] Percent-decode download filenames (#2584) Task/Issue URL: https://app.asana.com/0/1199230911884351/1202199027702557/f --- DuckDuckGo/Common/Extensions/StringExtension.swift | 4 ++++ DuckDuckGo/FileDownload/Extensions/WKWebView+Download.swift | 6 +----- DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/StringExtension.swift b/DuckDuckGo/Common/Extensions/StringExtension.swift index eeb8e5bedb..6128d9906a 100644 --- a/DuckDuckGo/Common/Extensions/StringExtension.swift +++ b/DuckDuckGo/Common/Extensions/StringExtension.swift @@ -71,6 +71,10 @@ extension String { return result } + func replacingInvalidFileNameCharacters(with replacement: String = "_") -> String { + replacingOccurrences(of: "[~#@*+%{}<>\\[\\]|\"\\_^\\/:\\\\]", with: replacement, options: .regularExpression) + } + init(_ staticString: StaticString) { self = staticString.withUTF8Buffer { String(decoding: $0, as: UTF8.self) diff --git a/DuckDuckGo/FileDownload/Extensions/WKWebView+Download.swift b/DuckDuckGo/FileDownload/Extensions/WKWebView+Download.swift index ad211f4eba..757f50f845 100644 --- a/DuckDuckGo/FileDownload/Extensions/WKWebView+Download.swift +++ b/DuckDuckGo/FileDownload/Extensions/WKWebView+Download.swift @@ -24,11 +24,7 @@ import WebKit extension WKWebView { var suggestedFilename: String? { - guard let title = self.title?.replacingOccurrences(of: "[~#@*+%{}<>\\[\\]|\"\\_^\\/:\\\\]", - with: "_", - options: .regularExpression), - !title.isEmpty - else { + guard let title = self.title?.replacingInvalidFileNameCharacters(), !title.isEmpty else { return url?.suggestedFilename } return title.appending(".html") diff --git a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift index a39f173fa1..ca3d3e26c9 100644 --- a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift +++ b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift @@ -560,7 +560,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { progress.totalUnitCount = response.expectedContentLength } - var suggestedFilename = suggestedFilename + var suggestedFilename = (suggestedFilename.removingPercentEncoding ?? suggestedFilename).replacingInvalidFileNameCharacters() // sometimes suggesteFilename has an extension appended to already present URL file extension // e.g. feed.xml.rss for www.domain.com/rss.xml if let urlSuggestedFilename = response.url?.suggestedFilename, From 77f8b144ff9b4357beab09463cc3af7a168854b6 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 10:39:07 +0500 Subject: [PATCH 23/40] Update Privacy Dashboard URL on navigation commit (#2583) Task/Issue URL: https://app.asana.com/0/1177771139624306/1205436170208663/f --- .../TabExtensions/PrivacyDashboardTabExtension.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/Tab/TabExtensions/PrivacyDashboardTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/PrivacyDashboardTabExtension.swift index ab33546a06..070374f0a2 100644 --- a/DuckDuckGo/Tab/TabExtensions/PrivacyDashboardTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/PrivacyDashboardTabExtension.swift @@ -136,13 +136,14 @@ extension PrivacyDashboardTabExtension: NavigationResponder { } @MainActor - func didReceiveRedirect(_ navigationAction: NavigationAction, for navigation: Navigation) { - resetDashboardInfo(for: navigationAction.url, didGoBackForward: false) + func didCommit(_ navigation: Navigation) { + resetDashboardInfo(for: navigation.url, didGoBackForward: navigation.navigationAction.navigationType.isBackForward) } - @MainActor - func didStart(_ navigation: Navigation) { - resetDashboardInfo(for: navigation.url, didGoBackForward: navigation.navigationAction.navigationType.isBackForward) + func navigationDidFinish(_ navigation: Navigation) { + if privacyInfo?.url != navigation.url { + resetDashboardInfo(for: navigation.url, didGoBackForward: navigation.navigationAction.navigationType.isBackForward) + } } } From b73b0b3f54bb8143861ee88f0fb2fa125d7c9334 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 12:02:29 +0500 Subject: [PATCH 24/40] Fix Open Downloads not working (#2576) Task/Issue URL: https://app.asana.com/0/1199230911884351/1207020636198102/f --- .../Model/DownloadViewModel.swift | 2 +- .../View/DownloadsViewController.swift | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift b/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift index cdcdadf330..d22e8a9e6e 100644 --- a/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift +++ b/DuckDuckGo/FileDownload/Model/DownloadViewModel.swift @@ -75,7 +75,7 @@ final class DownloadViewModel { } func update(with item: DownloadListItem) { - self.localURL = item.destinationURL + self.localURL = item.tempURL == nil ? item.destinationURL : nil // only return destination file URL for completed downloads self.filename = item.fileName let oldState = self.state let newState = State(item: item, shouldAnimateOnAppear: state.shouldAnimateOnAppear ?? true) diff --git a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift index fb33a0788d..dac2e0bec7 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift @@ -158,35 +158,43 @@ final class DownloadsViewController: NSViewController { @IBAction func openDownloadsFolderAction(_ sender: Any) { let prefs = DownloadsPreferences.shared + let downloads = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] var url: URL? var itemToSelect: URL? if prefs.alwaysRequestDownloadLocation { - url = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first + url = prefs.lastUsedCustomDownloadLocation + // reveal the last completed download if let lastDownloaded = viewModel.items.first/* last added */(where: { // should still exist - $0.localURL != nil && FileManager.default.fileExists(atPath: $0.localURL!.deletingLastPathComponent().path) + if let url = $0.localURL, FileManager.default.fileExists(atPath: url.path) { true } else { false } }), let lastDownloadedURL = lastDownloaded.localURL, - // if no downloads are from the default Downloads folder - open the last downloaded item folder !viewModel.items.contains(where: { $0.localURL?.deletingLastPathComponent().path == url?.path }) || url == nil { url = lastDownloadedURL.deletingLastPathComponent() // select last downloaded item itemToSelect = lastDownloadedURL - } /* else fallback to default User‘s Downloads */ + } /* else fallback to the last location chosen in the Save Panel */ } else { // open preferred downlod location - url = prefs.effectiveDownloadLocation ?? FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first + url = prefs.effectiveDownloadLocation } - guard let url else { return } + let folder = url ?? downloads + + _=NSWorkspace.shared.selectFile(itemToSelect?.path, inFileViewerRootedAtPath: folder.path) + // hack for the sandboxed environment: + // when we have no permission to open a folder we don‘t have access to + // try to guess a file that would most probably exist and reveal it: it‘s the ".DS_Store" file + || NSWorkspace.shared.selectFile(folder.appendingPathComponent(".DS_Store").path, inFileViewerRootedAtPath: folder.path) + // fallback to default Downloads folder + || NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: downloads.path) self.dismiss() - NSWorkspace.shared.selectFile(itemToSelect?.path, inFileViewerRootedAtPath: url.path) } @IBAction func clearDownloadsAction(_ sender: Any) { From c3b0c7ba6e42f682f696a80bbcec8777c68d1152 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 12:10:20 +0500 Subject: [PATCH 25/40] Allow choosing downloads location in App Store builds (#2532) Task/Issue URL: https://app.asana.com/0/1199230911884351/1205665411589753/f - https://app.asana.com/0/0/1205084529216312/f - https://app.asana.com/0/1199178362774117/1201154025406383/f - https://app.asana.com/0/1199230911884351/1207020636198094/f - https://app.asana.com/0/1199230911884351/1207020636198108/f --- Configuration/App/DuckDuckGo.xcconfig | 8 +- Configuration/AppStore.xcconfig | 8 +- DuckDuckGo.xcodeproj/project.pbxproj | 22 ++ .../Extensions/FileManagerExtension.swift | 12 + .../Common/Extensions/URLExtension.swift | 44 +++ DuckDuckGo/Common/Logging/Logging.swift | 42 +++ .../FileDownload/Model/FilePresenter.swift | 256 ++++++++++-------- .../Model/NSURL+sandboxExtensionRetainCount.m | 40 +++ .../SecurityScopedFileURLController.swift | 179 ++++++++++++ .../Model/WebKitDownloadTask.swift | 101 ++++--- .../Services/DownloadListCoordinator.swift | 32 ++- .../Model/DownloadsPreferences.swift | 64 +++-- .../View/PreferencesGeneralView.swift | 4 +- .../Tab/View/BrowserTabViewController.swift | 19 +- DuckDuckGo/Tab/View/WebView.swift | 1 + .../Downloads/DownloadsIntegrationTests.swift | 18 +- .../FileDownload/FilePresenterTests.swift | 81 ++---- .../DownloadsPreferencesTests.swift | 9 +- sandbox-test-tool/SandboxTestTool.swift | 39 ++- 19 files changed, 714 insertions(+), 265 deletions(-) create mode 100644 DuckDuckGo/FileDownload/Model/NSURL+sandboxExtensionRetainCount.m create mode 100644 DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift diff --git a/Configuration/App/DuckDuckGo.xcconfig b/Configuration/App/DuckDuckGo.xcconfig index e0490eb81d..778bc1e09d 100644 --- a/Configuration/App/DuckDuckGo.xcconfig +++ b/Configuration/App/DuckDuckGo.xcconfig @@ -34,10 +34,10 @@ PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*] = PROVISIONING_PROFILE_SPECIFIER[config=Release][sdk=macosx*] = MacOS Browser PROVISIONING_PROFILE_SPECIFIER[config=Review][sdk=macosx*] = MacOS Browser Product Review -GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 -GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 CI=1 $(inherited) -GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 $(inherited) -GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 REVIEW=1 $(inherited) +GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) +GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 CI=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited) +GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 DEBUG=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited) +GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION=1 REVIEW=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited) SWIFT_ACTIVE_COMPILATION_CONDITIONS[arch=*][sdk=*] = NETP_SYSTEM_EXTENSION $(FEATURE_FLAGS) SWIFT_ACTIVE_COMPILATION_CONDITIONS[config=CI][arch=*][sdk=*] = NETP_SYSTEM_EXTENSION DEBUG CI $(FEATURE_FLAGS) diff --git a/Configuration/AppStore.xcconfig b/Configuration/AppStore.xcconfig index 1fbb567afd..30ec838615 100644 --- a/Configuration/AppStore.xcconfig +++ b/Configuration/AppStore.xcconfig @@ -23,10 +23,10 @@ MAIN_BUNDLE_IDENTIFIER[config=Debug][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).d MAIN_BUNDLE_IDENTIFIER[config=CI][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).debug MAIN_BUNDLE_IDENTIFIER[config=Review][sdk=*] = $(MAIN_BUNDLE_IDENTIFIER_PREFIX).review -GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = APPSTORE=1 -GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 CI=1 $(inherited) -GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 $(inherited) -GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = APPSTORE=1 REVIEW=1 $(inherited) +GCC_PREPROCESSOR_DEFINITIONS[arch=*][sdk=*] = APPSTORE=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) +GCC_PREPROCESSOR_DEFINITIONS[config=CI][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 CI=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited) +GCC_PREPROCESSOR_DEFINITIONS[config=Debug][arch=*][sdk=*] = APPSTORE=1 DEBUG=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited) +GCC_PREPROCESSOR_DEFINITIONS[config=Review][arch=*][sdk=*] = APPSTORE=1 REVIEW=1 SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME) $(inherited) MACOSX_DEPLOYMENT_TARGET = 12.3 diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d7da5c939e..0ece6426d8 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -3063,6 +3063,12 @@ B6ABC5962B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; }; B6ABC5972B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; }; B6ABC5982B4861D4008343B9 /* FocusableTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */; }; + B6ABD0CA2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; }; + B6ABD0CB2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; }; + B6ABD0CC2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; }; + B6ABD0CE2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; }; + B6ABD0CF2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; }; + B6ABD0D02BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; }; B6AE39F129373AF200C37AA4 /* EmptyAttributionRulesProver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */; }; B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F229374AEC00C37AA4 /* OHHTTPStubs */; }; B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */ = {isa = PBXBuildFile; productRef = B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */; }; @@ -3203,6 +3209,9 @@ B6EC37FC29B83E99001ACE79 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; }; B6EC37FD29B83E99001ACE79 /* TestsURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EC37FB29B83E99001ACE79 /* TestsURLExtension.swift */; }; B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */ = {isa = PBXBuildFile; productRef = B6EC37FE29B8D915001ACE79 /* Configuration */; }; + B6EECB302BC3FA5A00B3CB77 /* SecurityScopedFileURLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */; }; + B6EECB312BC3FAB100B3CB77 /* NSURL+sandboxExtensionRetainCount.m in Sources */ = {isa = PBXBuildFile; fileRef = B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */; }; + B6EECB322BC40A1400B3CB77 /* FileManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E61EE2263AC0C8004E11AB /* FileManagerExtension.swift */; }; B6EEDD7D2B8C69E900637EBC /* TabContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */; }; B6EEDD7E2B8C69E900637EBC /* TabContentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6EEDD7C2B8C69E900637EBC /* TabContentTests.swift */; }; B6F1C80B2761C45400334924 /* LocalUnprotectedDomains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 336B39E22726B4B700C417D3 /* LocalUnprotectedDomains.swift */; }; @@ -4645,6 +4654,8 @@ B6AAAC2C260330580029438D /* PublishedAfter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublishedAfter.swift; sourceTree = ""; }; B6AAAC3D26048F690029438D /* RandomAccessCollectionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomAccessCollectionExtension.swift; sourceTree = ""; }; B6ABC5952B4861D4008343B9 /* FocusableTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusableTextField.swift; sourceTree = ""; }; + B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityScopedFileURLController.swift; sourceTree = ""; }; + B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSURL+sandboxExtensionRetainCount.m"; sourceTree = ""; }; B6AE39F029373AF200C37AA4 /* EmptyAttributionRulesProver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyAttributionRulesProver.swift; sourceTree = ""; }; B6AE74332609AFCE005B9B1A /* ProgressEstimationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressEstimationTests.swift; sourceTree = ""; }; B6B040072B95C4C80085279D /* Downloads 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "Downloads 2.xcdatamodel"; sourceTree = ""; }; @@ -6690,6 +6701,8 @@ B6C0B23826E742610031CB7F /* FileDownloadError.swift */, 856C98DE257014BD00A22F1F /* FileDownloadManager.swift */, B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */, + B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */, + B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */, B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */, B6CC266B2BAD9CD800F53F8D /* FileProgressPresenter.swift */, ); @@ -10727,6 +10740,7 @@ 7B430EA22A71411A00BAC4A1 /* NetworkProtectionSimulateFailureMenu.swift in Sources */, 3706FBD5293F65D500E42796 /* TabCollection+NSSecureCoding.swift in Sources */, 3706FBD6293F65D500E42796 /* Instruments.swift in Sources */, + B6ABD0CF2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */, B62B483F2ADE48DE000DECE5 /* ArrayBuilder.swift in Sources */, 569277C229DDCBB500B633EF /* HomePageContinueSetUpModel.swift in Sources */, 3706FBD7293F65D500E42796 /* ContentBlockerRulesLists.swift in Sources */, @@ -10872,6 +10886,7 @@ 3706FC32293F65D500E42796 /* MoreOptionsMenu.swift in Sources */, 3706FC34293F65D500E42796 /* PermissionAuthorizationViewController.swift in Sources */, 3706FC35293F65D500E42796 /* BookmarkNode.swift in Sources */, + B6ABD0CB2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */, 31EF1E822B63FFC200E6DB17 /* DataBrokerProtectionLoginItemScheduler.swift in Sources */, B6B140892ABDBCC1004F8E85 /* HoverTrackingArea.swift in Sources */, 3706FC36293F65D500E42796 /* LongPressButton.swift in Sources */, @@ -12027,6 +12042,7 @@ 4B2F565D2B38F93E001214C0 /* NetworkProtectionSubscriptionEventHandler.swift in Sources */, 4B957B082AC7AE700062CA31 /* PixelDataStore.swift in Sources */, 4B957B092AC7AE700062CA31 /* WaitlistStorage.swift in Sources */, + B6ABD0D02BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */, 4B957B0A2AC7AE700062CA31 /* Pixel.swift in Sources */, 4B957B0B2AC7AE700062CA31 /* PixelEvent.swift in Sources */, 4B957B0C2AC7AE700062CA31 /* TabBarFooter.swift in Sources */, @@ -12051,6 +12067,7 @@ F1B33DF42BAD929D001128B3 /* SubscriptionAppStoreRestorer.swift in Sources */, 4B957B1B2AC7AE700062CA31 /* ScriptSourceProviding.swift in Sources */, 4B957B1C2AC7AE700062CA31 /* CoreDataBookmarkImporter.swift in Sources */, + B6ABD0CC2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */, 4B957B1D2AC7AE700062CA31 /* SuggestionViewModel.swift in Sources */, 4B957B1E2AC7AE700062CA31 /* BookmarkManagedObject.swift in Sources */, 4B957B1F2AC7AE700062CA31 /* CSVLoginExporter.swift in Sources */, @@ -12895,6 +12912,7 @@ 4B37EE5F2B4CFC3C00A89A61 /* HomePageRemoteMessagingStorage.swift in Sources */, 4B9DB0472A983B24000927DB /* WaitlistRootView.swift in Sources */, 31F28C5128C8EEC500119F70 /* YoutubeOverlayUserScript.swift in Sources */, + B6ABD0CA2BC03F610000EB69 /* SecurityScopedFileURLController.swift in Sources */, B6040856274B830F00680351 /* DictionaryExtension.swift in Sources */, B684592725C93C0500DC17B6 /* Publishers.NestedObjectChanges.swift in Sources */, B6DA06E62913F39400225DE2 /* MenuItemSelectors.swift in Sources */, @@ -13016,6 +13034,7 @@ B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */, 4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */, 1D36F4242A3B85C50052B527 /* TabCleanupPreparer.swift in Sources */, + B6ABD0CE2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m in Sources */, 4B4D60DF2A0C875F00BCD287 /* NetworkProtectionOptionKeyExtension.swift in Sources */, 85AC3AEF25D5CE9800C7D2AA /* UserScripts.swift in Sources */, B643BF1427ABF772000BACEC /* NSWorkspaceExtension.swift in Sources */, @@ -13378,7 +13397,10 @@ B6E6BA062BA1FE10008AA7E1 /* NSApplicationExtension.swift in Sources */, B6E6B9F62BA1FD90008AA7E1 /* SandboxTestTool.swift in Sources */, B6E6BA252BA2EDDE008AA7E1 /* FileReadResult.swift in Sources */, + B6EECB322BC40A1400B3CB77 /* FileManagerExtension.swift in Sources */, + B6EECB302BC3FA5A00B3CB77 /* SecurityScopedFileURLController.swift in Sources */, B6E6BA052BA1FE09008AA7E1 /* URLExtension.swift in Sources */, + B6EECB312BC3FAB100B3CB77 /* NSURL+sandboxExtensionRetainCount.m in Sources */, B6E6BA202BA2E462008AA7E1 /* CollectionExtension.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DuckDuckGo/Common/Extensions/FileManagerExtension.swift b/DuckDuckGo/Common/Extensions/FileManagerExtension.swift index c524d39c8c..b7e767fece 100644 --- a/DuckDuckGo/Common/Extensions/FileManagerExtension.swift +++ b/DuckDuckGo/Common/Extensions/FileManagerExtension.swift @@ -101,4 +101,16 @@ extension FileManager { return resolvedUrl.path.hasPrefix(trashUrl.path) } + /// Check if location pointed by the URL is writable by writing an empty data to it and removing the file if write succeeds + /// - Throws error if writing to the location fails + func checkWritability(_ url: URL) throws { + if fileExists(atPath: url.path), isWritableFile(atPath: url.path) { + return // we can write + } else { + // either we can‘t write or there‘s no file at the url – try writing throwing access error if no permission + try Data().write(to: url) + try removeItem(at: url) + } + } + } diff --git a/DuckDuckGo/Common/Extensions/URLExtension.swift b/DuckDuckGo/Common/Extensions/URLExtension.swift index 6ea129d2fb..b79720a9d5 100644 --- a/DuckDuckGo/Common/Extensions/URLExtension.swift +++ b/DuckDuckGo/Common/Extensions/URLExtension.swift @@ -469,6 +469,50 @@ extension URL { } + var isFileHidden: Bool { + get throws { + try self.resourceValues(forKeys: [.isHiddenKey]).isHidden ?? false + } + } + + mutating func setFileHidden(_ hidden: Bool) throws { + var resourceValues = URLResourceValues() + resourceValues.isHidden = true + try setResourceValues(resourceValues) + } + + /// Check if location pointed by the URL is writable + /// - Note: if there‘s no file at the URL, it will try to create a file and then remove it + func isWritableLocation() -> Bool { + do { + try FileManager.default.checkWritability(self) + return true + } catch { + return false + } + } + +#if DEBUG && APPSTORE + /// sandbox extension URL access should be stopped after SecurityScopedFileURLController is deallocated - this function validates it and breaks if the file is still writable + func ensureUrlIsNotWritable(or handler: () -> Void) { + let fm = FileManager.default + // is the URL ~/Downloads? + if self.resolvingSymlinksInPath() == fm.urls(for: .downloadsDirectory, in: .userDomainMask).first!.resolvingSymlinksInPath() { + assert(isWritableLocation()) + return + } + // is parent directory writable (e.g. ~/Downloads)? + if fm.isWritableFile(atPath: self.deletingLastPathComponent().path) + // trashed files are still accessible for some reason even after stopping access + || fm.isInTrash(self) + // other file is being saved at the same URL + || NSURL.activeSecurityScopedUrlUsages.contains(where: { $0.url !== self as NSURL && $0.url == self as NSURL }) + || !isWritableLocation() { return } + + handler() + } +#endif + // MARK: - System Settings static var fullDiskAccess = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")! diff --git a/DuckDuckGo/Common/Logging/Logging.swift b/DuckDuckGo/Common/Logging/Logging.swift index de04f91cb7..3740e0e223 100644 --- a/DuckDuckGo/Common/Logging/Logging.swift +++ b/DuckDuckGo/Common/Logging/Logging.swift @@ -140,3 +140,45 @@ func logOrAssertionFailure(_ message: String) { os_log("%{public}s", type: .error, message) #endif } + +#if DEBUG + +func breakByRaisingSigInt(_ description: String, file: StaticString = #file, line: Int = #line) { + let fileLine = "\(("\(file)" as NSString).lastPathComponent):\(line)" + os_log(""" + + + ------------------------------------------------------------------------------------------------------ + BREAK at %s: + ------------------------------------------------------------------------------------------------------ + + %s + + Hit Continue (^⌘Y) to continue program execution + ------------------------------------------------------------------------------------------------------ + + """, type: .debug, fileLine, description.components(separatedBy: "\n").map { " " + $0.trimmingWhitespace() }.joined(separator: "\n")) + raise(SIGINT) +} + +// get symbol from stack trace for a caller of a calling method +func callingSymbol() -> String { + let stackTrace = Thread.callStackSymbols + // find `callingSymbol` itself or dispatch_once_callout + var callingSymbolIdx = stackTrace.firstIndex(where: { $0.contains("_dispatch_once_callout") }) + ?? stackTrace.firstIndex(where: { $0.contains("callingSymbol") })! + // procedure calling `callingSymbol` + callingSymbolIdx += 1 + + var symbolName: String + repeat { + // caller for the procedure + callingSymbolIdx += 1 + let line = stackTrace[callingSymbolIdx].replacingOccurrences(of: Bundle.main.executableURL!.lastPathComponent, with: "DDG") + symbolName = String(line.split(separator: " ", maxSplits: 3)[3]).components(separatedBy: " + ")[0] + } while stackTrace[callingSymbolIdx - 1].contains(symbolName.dropping(suffix: "To")) // skip objc wrappers + + return symbolName +} + +#endif diff --git a/DuckDuckGo/FileDownload/Model/FilePresenter.swift b/DuckDuckGo/FileDownload/Model/FilePresenter.swift index be8d94e8a6..1770935af2 100644 --- a/DuckDuckGo/FileDownload/Model/FilePresenter.swift +++ b/DuckDuckGo/FileDownload/Model/FilePresenter.swift @@ -23,7 +23,6 @@ import Foundation private protocol FilePresenterDelegate: AnyObject { var logger: FilePresenterLogger { get } var url: URL? { get } - var primaryPresentedItemURL: URL? { get } func presentedItemDidMove(to newURL: URL) func accommodatePresentedItemDeletion() throws func accommodatePresentedItemEviction() throws @@ -57,12 +56,14 @@ internal class FilePresenter { final let presentedItemOperationQueue: OperationQueue fileprivate final weak var delegate: FilePresenterDelegate? - init(presentedItemOperationQueue: OperationQueue) { + init(presentedItemOperationQueue: OperationQueue, delegate: FilePresenterDelegate) { self.presentedItemOperationQueue = presentedItemOperationQueue + self.delegate = delegate } + final var fallbackPresentedItemURL: URL? final var presentedItemURL: URL? { - guard let delegate else { return nil } + guard let delegate else { return fallbackPresentedItemURL } FilePresenter.dispatchSourceQueue.async { // prevent owning FilePresenter deallocation inside the presentedItemURL getter withExtendedLifetime(delegate) {} @@ -72,12 +73,10 @@ internal class FilePresenter { } final func presentedItemDidMove(to newURL: URL) { - assert(delegate != nil) delegate?.presentedItemDidMove(to: newURL) } func accommodatePresentedItemDeletion(completionHandler: @escaping @Sendable ((any Error)?) -> Void) { - assert(delegate != nil) do { try delegate?.accommodatePresentedItemDeletion() completionHandler(nil) @@ -87,9 +86,8 @@ internal class FilePresenter { } func accommodatePresentedItemEviction(completionHandler: @escaping @Sendable ((any Error)?) -> Void) { - assert(delegate != nil) do { - try delegate?.accommodatePresentedItemEviction() + try delegate?.accommodatePresentedItemEviction() completionHandler(nil) } catch { completionHandler(error) @@ -100,74 +98,138 @@ internal class FilePresenter { final private class DelegatingRelatedFilePresenter: DelegatingFilePresenter { - var primaryPresentedItemURL: URL? { - let url = delegate?.primaryPresentedItemURL - return url + let primaryPresentedItemURL: URL? + + init(primaryPresentedItemURL: URL?, presentedItemOperationQueue: OperationQueue, delegate: FilePresenterDelegate) { + self.primaryPresentedItemURL = primaryPresentedItemURL + super.init(presentedItemOperationQueue: presentedItemOperationQueue, delegate: delegate) } } fileprivate let lock = NSLock() - private var innerPresenter: DelegatingFilePresenter? + private var innerPresenters = [DelegatingFilePresenter]() private var dispatchSourceCancellable: AnyCancellable? fileprivate let logger: any FilePresenterLogger - let primaryPresentedItemURL: URL? - - private var _url: URL? + private var urlController: SecurityScopedFileURLController? final var url: URL? { lock.withLock { - _url + urlController?.url } } private func setURL(_ newURL: URL?) { - guard let oldValue = lock.withLock({ () -> URL?? in - let oldValue = _url - guard oldValue != newURL else { return URL??.none } - _url = newURL - return oldValue - }) else { return } + guard let oldValue = lock.withLock({ _setURL(newURL) }) else { return } didSetURL(newURL, oldValue: oldValue) } + // inside locked scope + private func _setURL(_ newURL: URL?) -> URL?? /* returns old value (URL?) if new value was updated */ { + let oldValue = urlController?.url + guard oldValue != newURL else { return URL??.none } + guard let newURL else { + urlController = nil + return newURL + } + + // if the new url is pointing to the same path (only letter case has changed) - keep its sandbox extension in a new Controller + if let urlController, let oldValue, + oldValue.resolvingSymlinksInPath().path == newURL.resolvingSymlinksInPath().path, + urlController.isManagingSecurityScope { + urlController.updateUrlKeepingSandboxExtensionRetainCount(newURL) + } else { + urlController = SecurityScopedFileURLController(url: newURL, logger: logger) + } + + return oldValue + } + private var urlSubject = PassthroughSubject() final var urlPublisher: AnyPublisher { urlSubject.prepend(url).eraseToAnyPublisher() } - init(url: URL, primaryItemURL: URL? = nil, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { - self._url = url - self.primaryPresentedItemURL = primaryItemURL + /// - Parameter url: represented file URL access to which is coordinated by the File Presenter. + /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). + /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. + /// - Note: see https://stackoverflow.com/questions/25627628/sandboxed-mac-app-exhausting-security-scoped-url-resources + init(url: URL, consumeUnbalancedStartAccessingResource: Bool = false, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + self.urlController = SecurityScopedFileURLController(url: url, manageSecurityScope: consumeUnbalancedStartAccessingResource, logger: logger) + self.logger = logger - let innerPresenter: DelegatingFilePresenter - if primaryItemURL != nil { - innerPresenter = DelegatingRelatedFilePresenter(presentedItemOperationQueue: FilePresenter.presentedItemOperationQueue) + do { + try setupInnerPresenter(for: url, primaryItemURL: nil, createIfNeededCallback: createIfNeededCallback) + logger.log("🗄️ added file presenter for \"\(url.path)\"") + } catch { + removeFilePresenters() + throw error + } + } + + /// - Parameter url: represented file URL access to which is coordinated by the File Presenter. + /// - Parameter primaryItemURL: URL to a main file resource access to which has been granted. + /// Used to grant out-of-sandbox access to `url` representing a “related” resource like “download.duckload” where the `primaryItemURL` would point to “download.zip”. + /// - Note: the related (“duckload”) file extension should be registered in the Info.plist with `NSIsRelatedItemType` flag set to `true`. + /// - Note: when presenting a related item the security scoped resource access will always be stopped on the File Presenter deallocation + /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). + /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. + init(url: URL, primaryItemURL: URL, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + self.urlController = SecurityScopedFileURLController(url: url, logger: logger) + self.logger = logger + + do { + try setupInnerPresenter(for: url, primaryItemURL: primaryItemURL, createIfNeededCallback: createIfNeededCallback) + logger.log("🗄️ added file presenter for \"\(url.path) primary item: \"\(primaryItemURL.path)\"") + } catch { + removeFilePresenters() + throw error + } + } + + private func setupInnerPresenter(for url: URL, primaryItemURL: URL?, createIfNeededCallback: ((URL) throws -> URL)?) throws { + let innerPresenter = if let primaryItemURL { + DelegatingRelatedFilePresenter(primaryPresentedItemURL: primaryItemURL, presentedItemOperationQueue: FilePresenter.presentedItemOperationQueue, delegate: self) } else { - innerPresenter = DelegatingFilePresenter(presentedItemOperationQueue: FilePresenter.presentedItemOperationQueue) + DelegatingFilePresenter(presentedItemOperationQueue: FilePresenter.presentedItemOperationQueue, delegate: self) } - self.innerPresenter = innerPresenter - innerPresenter.delegate = self + self.innerPresenters = [innerPresenter] + NSFileCoordinator.addFilePresenter(innerPresenter) if !FileManager.default.fileExists(atPath: url.path) { - if let createFile = createIfNeededCallback { - logger.log("🗄️💥 creating file for presenter at \"\(url.path)\"") - self._url = try coordinateWrite(at: url, using: createFile) - - // re-add File Presenter for the updated URL + guard let createFile = createIfNeededCallback else { + throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path]) + } + logger.log("🗄️💥 creating file for presenter at \"\(url.path)\"") + // create new file at the presented URL using the provided callback and update URL if needed + _=self._setURL( + try coordinateWrite(at: url, using: createFile) + ) + + if primaryItemURL == nil { + // Remove and re-add the file presenter for regular item presenters. NSFileCoordinator.removeFilePresenter(innerPresenter) NSFileCoordinator.addFilePresenter(innerPresenter) - - } else { - throw CocoaError(.fileReadNoSuchFile, userInfo: [NSFilePathErrorKey: url.path]) } } - addFSODispatchSource(for: url) + // to correctly handle file move events for a “related” item presenters we need to use a secondary presenter + if primaryItemURL != nil { + // set permanent original url without tracking file movements + // to correctly release the sandbox extension when the “related” presenter is removed + innerPresenter.fallbackPresentedItemURL = url + innerPresenter.delegate = nil + + let innerPresenter2 = DelegatingFilePresenter(presentedItemOperationQueue: FilePresenter.presentedItemOperationQueue, delegate: self) + NSFileCoordinator.addFilePresenter(innerPresenter2) + innerPresenters.append(innerPresenter2) + } - logger.log("🗄️ added file presenter for \"\(url.path)\"\(primaryPresentedItemURL != nil ? " primary item: \"\(primaryPresentedItemURL!.path)\"" : "")") + try coordinateRead(at: url, with: .withoutChanges) { url in + addFSODispatchSource(for: url) + } } private func addFSODispatchSource(for url: URL) { @@ -187,7 +249,7 @@ internal class FilePresenter { self.logger.log("🗄️⚠️ file delete event handler: \"\(url.path)\"") var resolvedBookmarkData: URL? { var isStale = false - guard let presenter = self as? SandboxFilePresenter, + guard let presenter = self as? BookmarkFilePresenter, let bookmarkData = presenter.fileBookmarkData, let url = try? URL(resolvingBookmarkData: bookmarkData, bookmarkDataIsStale: &isStale) else { if FileManager().fileExists(atPath: url.path) { return url } // file still exists but with different letter case ? @@ -212,25 +274,36 @@ internal class FilePresenter { dispatchSource.resume() } - private func removeFilePresenter() { - if let innerPresenter { - logger.log("🗄️ removing file presenter for \"\(url?.path ?? "")\"") + private func removeFilePresenters() { + for (idx, innerPresenter) in innerPresenters.enumerated() { + // innerPresenter delegate won‘t be available at this point when called from `deinit`, + // so set the final url here to correctly remove the presenter. + if innerPresenter.fallbackPresentedItemURL == nil { + innerPresenter.fallbackPresentedItemURL = urlController?.url + } + logger.log("🗄️ removing file presenter \(idx) for \"\(innerPresenter.presentedItemURL?.path ?? "")\"") NSFileCoordinator.removeFilePresenter(innerPresenter) - self.innerPresenter = nil } + if innerPresenters.count > 1 { + // ”related” item File Presenters make an unbalanced sandbox extension retain, + // release the actual file URL sandbox extension by calling an extra `stopAccessingSecurityScopedResource` + urlController?.url.consumeUnbalancedStartAccessingSecurityScopedResource() + } + innerPresenters = [] } fileprivate func didSetURL(_ newValue: URL?, oldValue: URL?) { - assert(newValue != oldValue) + assert(newValue == nil || newValue != oldValue) logger.log("🗄️ did update url from \"\(oldValue?.path ?? "")\" to \"\(newValue?.path ?? "")\"") urlSubject.send(newValue) } deinit { - removeFilePresenter() + removeFilePresenters() } } + extension FilePresenter: FilePresenterDelegate { func presentedItemDidMove(to newURL: URL) { @@ -240,8 +313,9 @@ extension FilePresenter: FilePresenterDelegate { func accommodatePresentedItemDeletion() throws { logger.log("🗄️ accommodatePresentedItemDeletion (\"\(url?.path ?? "")\")") + // should go before resetting the URL to correctly remove File Presenter + removeFilePresenters() setURL(nil) - removeFilePresenter() } func accommodatePresentedItemEviction() throws { @@ -254,9 +328,7 @@ extension FilePresenter: FilePresenterDelegate { /// Maintains File Bookmark Data for presented resource URL /// and manages its sandbox security scope access calling `stopAccessingSecurityScopedResource` on deinit /// balanced with preceding `startAccessingSecurityScopedResource` -final class SandboxFilePresenter: FilePresenter { - - private let securityScopedURL: URL? +final class BookmarkFilePresenter: FilePresenter { private var _fileBookmarkData: Data? final var fileBookmarkData: Data? { @@ -271,21 +343,31 @@ final class SandboxFilePresenter: FilePresenter { } /// - Parameter url: represented file URL access to which is coordinated by the File Presenter. - /// - Parameter primaryItemURL: URL to a main file resource access to which has been granted. - /// Used to grant out-of-sandbox access to `url` representing a “secondary” resource like “download.duckload” where the `primaryItemURL` would point to “download.zip”. - /// - Note: the secondary (“duckload”) file extension should be registered in the Info.plist with `NSIsRelatedItemType` flag set to `true`. /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. - init(url: URL, primaryItemURL: URL? = nil, consumeUnbalancedStartAccessingResource: Bool = false, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { + override init(url: URL, consumeUnbalancedStartAccessingResource: Bool = false, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { - if consumeUnbalancedStartAccessingResource || url.startAccessingSecurityScopedResource() == true { - self.securityScopedURL = url - logger.log("🏝️ \(consumeUnbalancedStartAccessingResource ? "consuming unbalanced startAccessingResource for" : "started resource access for") \"\(url.path)\"") - } else { - self.securityScopedURL = nil - logger.log("🏖️ didn‘t start resource access for \"\(url.path)\"") + try super.init(url: url, consumeUnbalancedStartAccessingResource: consumeUnbalancedStartAccessingResource, logger: logger, createIfNeededCallback: createIfNeededCallback) + + do { + try self.coordinateRead(at: url, with: .withoutChanges) { url in + logger.log("📒 updating bookmark data for \"\(url.path)\"") + self._fileBookmarkData = try url.bookmarkData(options: .withSecurityScope) + } + } catch { + logger.log("📕 bookmark data retreival failed for \"\(url.path)\": \(error)") + throw error } + } + /// - Parameter url: represented file URL access to which is coordinated by the File Presenter. + /// - Parameter primaryItemURL: URL to a main file resource access to which has been granted. + /// Used to grant out-of-sandbox access to `url` representing a “related” resource like “download.duckload” where the `primaryItemURL` would point to “download.zip”. + /// - Note: the related (“duckload”) file extension should be registered in the Info.plist with `NSIsRelatedItemType` flag set to `true`. + /// - Note: when presenting a related item the security scoped resource access will always be stopped on the File Presenter deallocation + /// - Parameter consumeUnbalancedStartAccessingResource: assume the `url` is already accessible (e.g. after choosing the file using Open Panel). + /// would cause an unbalanced `stopAccessingSecurityScopedResource` call on the File Presenter deallocation. + override init(url: URL, primaryItemURL: URL, logger: FilePresenterLogger = OSLog.disabled, createIfNeededCallback: ((URL) throws -> URL)? = nil) throws { try super.init(url: url, primaryItemURL: primaryItemURL, logger: logger, createIfNeededCallback: createIfNeededCallback) do { @@ -305,15 +387,8 @@ final class SandboxFilePresenter: FilePresenter { var isStale = false logger.log("📒 resolving url from bookmark data") let url = try URL(resolvingBookmarkData: fileBookmarkData, options: .withSecurityScope, bookmarkDataIsStale: &isStale) - if url.startAccessingSecurityScopedResource() == true { - self.securityScopedURL = url - logger.log("🏝️ started resource access for \"\(url.path)\"\(isStale ? " (stale)" : "")") - } else { - self.securityScopedURL = nil - logger.log("🏖️ didn‘t start resource access for \"\(url.path)\"\(isStale ? " (stale)" : "")") - } - try super.init(url: url, logger: logger) + try super.init(url: url, consumeUnbalancedStartAccessingResource: true, logger: logger) if isStale { DispatchQueue.global().async { [weak self] in @@ -346,25 +421,18 @@ final class SandboxFilePresenter: FilePresenter { fileBookmarkDataSubject.send(fileBookmarkData) } - deinit { - if let securityScopedURL { - logger.log("🗄️ stopAccessingSecurityScopedResource \"\(securityScopedURL.path)\"") - securityScopedURL.stopAccessingSecurityScopedResource() - } - } - } extension FilePresenter { func coordinateRead(at url: URL? = nil, with options: NSFileCoordinator.ReadingOptions = [], using reader: (URL) throws -> T) throws -> T { - guard let innerPresenter, let url = url ?? self.url else { throw CocoaError(.fileNoSuchFile) } + guard let innerPresenter = innerPresenters.last, let url = url ?? self.url else { throw CocoaError(.fileNoSuchFile) } return try NSFileCoordinator(filePresenter: innerPresenter).coordinateRead(at: url, with: options, using: reader) } func coordinateWrite(at url: URL? = nil, with options: NSFileCoordinator.WritingOptions = [], using writer: (URL) throws -> T) throws -> T { - guard let innerPresenter, let url = url ?? self.url else { throw CocoaError(.fileNoSuchFile) } + guard let innerPresenter = innerPresenters.last, let url = url ?? self.url else { throw CocoaError(.fileNoSuchFile) } // temporarily disable DispatchSource file removal events dispatchSourceCancellable?.cancel() @@ -377,7 +445,7 @@ extension FilePresenter { } public func coordinateMove(from url: URL? = nil, to: URL, with options2: NSFileCoordinator.WritingOptions = .forReplacing, using move: (URL, URL) throws -> T) throws -> T { - guard let innerPresenter, let url = url ?? self.url else { throw CocoaError(.fileNoSuchFile) } + guard let innerPresenter = innerPresenters.last, let url = url ?? self.url else { throw CocoaError(.fileNoSuchFile) } return try NSFileCoordinator(filePresenter: innerPresenter).coordinateMove(from: url, to: to, with: options2, using: move) } @@ -425,33 +493,3 @@ extension NSFileCoordinator { } } - -#if DEBUG -extension NSURL { - - private static var stopAccessingSecurityScopedResourceCallback: ((URL) -> Void)? - - private static let originalStopAccessingSecurityScopedResource = { - class_getInstanceMethod(NSURL.self, #selector(NSURL.stopAccessingSecurityScopedResource))! - }() - private static let swizzledStopAccessingSecurityScopedResource = { - class_getInstanceMethod(NSURL.self, #selector(NSURL.swizzled_stopAccessingSecurityScopedResource))! - }() - private static let swizzleStopAccessingSecurityScopedResourceOnce: Void = { - method_exchangeImplementations(originalStopAccessingSecurityScopedResource, swizzledStopAccessingSecurityScopedResource) - }() - - static func swizzleStopAccessingSecurityScopedResource(with stopAccessingSecurityScopedResourceCallback: ((URL) -> Void)?) { - _=swizzleStopAccessingSecurityScopedResourceOnce - self.stopAccessingSecurityScopedResourceCallback = stopAccessingSecurityScopedResourceCallback - } - - @objc private dynamic func swizzled_stopAccessingSecurityScopedResource() { - if let stopAccessingSecurityScopedResourceCallback = Self.stopAccessingSecurityScopedResourceCallback { - stopAccessingSecurityScopedResourceCallback(self as URL) - } - self.swizzled_stopAccessingSecurityScopedResource() // call original - } - -} -#endif diff --git a/DuckDuckGo/FileDownload/Model/NSURL+sandboxExtensionRetainCount.m b/DuckDuckGo/FileDownload/Model/NSURL+sandboxExtensionRetainCount.m new file mode 100644 index 0000000000..8a312c2fb0 --- /dev/null +++ b/DuckDuckGo/FileDownload/Model/NSURL+sandboxExtensionRetainCount.m @@ -0,0 +1,40 @@ +// +// NSURL+sandboxExtensionRetainCount.m +// +// 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 + +// Macro for adding quotes +#define STRINGIFY(X) STRINGIFY2(X) +#define STRINGIFY2(X) #X + +#import STRINGIFY(SWIFT_OBJC_INTERFACE_HEADER_NAME) + +@implementation NSURL (sandboxExtensionRetainCount) + +/** + * This method will be automatically called at app launch time to swizzle `startAccessingSecurityScopedResource` and + * `stopAccessingSecurityScopedResource` methods to accurately reflect the current number of start and stop calls + * stored in the associated `NSURL.sandboxExtensionRetainCount` value. + * + * See SecurityScopedFileURLController.swift + */ ++ (void)initialize { + [self swizzleStartStopAccessingSecurityScopedResourceOnce]; +} + +@end diff --git a/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift b/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift new file mode 100644 index 0000000000..7cdc7eecdf --- /dev/null +++ b/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift @@ -0,0 +1,179 @@ +// +// SecurityScopedFileURLController.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import Common + +/// Manages security-scoped resource access to a file URL. +/// +/// This class is designed to consume unbalanced `startAccessingSecurityScopedResource` calls and ensure proper +/// resource cleanup by calling `stopAccessingSecurityScopedResource` the appropriate number of times +/// to end the resource access securely. +/// +/// - Note: Used in conjunction with NSURL extension swizzling the `startAccessingSecurityScopedResource` and +/// `stopAccessingSecurityScopedResource` methods to accurately reflect the current number of start and stop calls. +/// The number is reflected in the associated `URL.sandboxExtensionRetainCount` value. +final class SecurityScopedFileURLController { + + fileprivate let logger: any FilePresenterLogger + + private(set) var url: URL + let isManagingSecurityScope: Bool + + /// Initializes a new instance of `SecurityScopedFileURLController` with the provided URL and security-scoped resource handling options. + /// + /// - Parameters: + /// - url: The URL of the file to manage. + /// - manageSecurityScope: A Boolean value indicating whether the controller should manage the URL security scope access (i.e. call stop and end accessing resource methods). + /// - logger: An optional logger instance for logging file operations. Defaults to disabled. + /// - Note: when `manageSecurityScope` is `true` access to the represented URL will be stopped for the whole app on the controller deallocation. + init(url: URL, manageSecurityScope: Bool = true, logger: any FilePresenterLogger = OSLog.disabled) { + assert(url.isFileURL) +#if APPSTORE + let didStartAccess = manageSecurityScope && url.startAccessingSecurityScopedResource() +#else + let didStartAccess = false +#endif + self.url = url + self.isManagingSecurityScope = didStartAccess + self.logger = logger + logger.log("\(didStartAccess ? "🧪 " : "")SecurityScopedFileURLController.init: \(url.sandboxExtensionRetainCount) – \"\(url.path)\"") + } + + func updateUrlKeepingSandboxExtensionRetainCount(_ newURL: URL) { + guard newURL as NSURL !== url as NSURL else { return } + + for _ in 0.. Bool { + if self.swizzled_startAccessingSecurityScopedResource() /* call original */ { + sandboxExtensionRetainCount += 1 + return true + } + return false + } + + @objc private dynamic func swizzled_stopAccessingSecurityScopedResource() { + self.swizzled_stopAccessingSecurityScopedResource() // call original + + var sandboxExtensionRetainCount = self.sandboxExtensionRetainCount + if sandboxExtensionRetainCount > 0 { + sandboxExtensionRetainCount -= 1 + self.sandboxExtensionRetainCount = sandboxExtensionRetainCount + } + } + + private static let sandboxExtensionRetainCountKey = UnsafeRawPointer(bitPattern: "sandboxExtensionRetainCountKey".hashValue)! + fileprivate(set) var sandboxExtensionRetainCount: Int { + get { + (objc_getAssociatedObject(self, Self.sandboxExtensionRetainCountKey) as? NSNumber)?.intValue ?? 0 + } + set { + objc_setAssociatedObject(self, Self.sandboxExtensionRetainCountKey, NSNumber(value: newValue), .OBJC_ASSOCIATION_RETAIN) +#if DEBUG + if newValue > 0 { + NSURL.activeSecurityScopedUrlUsages.insert(.init(url: self)) + } else { + NSURL.activeSecurityScopedUrlUsages.remove(.init(url: self)) + } +#endif + } + } + +#if DEBUG + struct SecurityScopedUrlUsage: Hashable { + let url: NSURL + // hash url as object address + func hash(into hasher: inout Hasher) { + hasher.combine(ObjectIdentifier(url)) + } + } + static var activeSecurityScopedUrlUsages: Set = [] +#endif + +} + +extension URL { + + /// The number of times the security-scoped resource associated with the URL has been accessed + /// using `startAccessingSecurityScopedResource` without a corresponding call to + /// `stopAccessingSecurityScopedResource`. This property provides a count of active accesses + /// to the security-scoped resource, helping manage resource cleanup and ensure proper + /// handling of security-scoped resources. + /// + /// - Note: Accessing this property requires NSURL extension swizzling of `startAccessingSecurityScopedResource` + /// and `stopAccessingSecurityScopedResource` methods to accurately track the count. + var sandboxExtensionRetainCount: Int { + (self as NSURL).sandboxExtensionRetainCount + } + + func consumeUnbalancedStartAccessingSecurityScopedResource() { + (self as NSURL).sandboxExtensionRetainCount += 1 + } + +} diff --git a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift index ca3d3e26c9..b263df5915 100644 --- a/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift +++ b/DuckDuckGo/FileDownload/Model/WebKitDownloadTask.swift @@ -116,6 +116,7 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable @MainActor private var itemReplacementDirectory: URL? @MainActor private var itemReplacementDirectoryFSOCancellable: AnyCancellable? @MainActor private var tempFileUrlCancellable: AnyCancellable? + @MainActor private(set) var selectedDestinationURL: URL? var originalRequest: URLRequest? { download.originalRequest @@ -226,6 +227,13 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable do { let fm = FileManager() guard let destinationURL else { throw URLError(.cancelled) } + // in case we‘re overwriting the URL – increment the access counter for the duration of the method + let accessStarted = destinationURL.startAccessingSecurityScopedResource() + defer { + if accessStarted { + destinationURL.stopAccessingSecurityScopedResource() + } + } os_log(.debug, log: log, "download task callback: creating temp directory for \"\(destinationURL.path)\"") switch cleanupStyle { @@ -333,15 +341,15 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable /// opens File Presenters for destination file and temp file private nonisolated func filePresenters(for destinationURL: URL, tempURL: URL) async throws -> (tempFile: FilePresenter, destinationFile: FilePresenter) { var destinationURL = destinationURL - let duckloadURL = destinationURL.deletingPathExtension().appendingPathExtension(Self.downloadExtension) - let fm = FileManager.default + var duckloadURL = destinationURL.deletingPathExtension().appendingPathExtension(Self.downloadExtension) + let fm = FileManager() // 🧙‍♂️ now we‘re doing do some magique here 🧙‍♂️ // -------------------------------------- os_log(.debug, log: log, "🧙‍♂️ magique.start: \"\(destinationURL.path)\" (\"\(duckloadURL.path)\") directory writable: \(fm.isWritableFile(atPath: destinationURL.deletingLastPathComponent().path))") // 1. create our final destination file (let‘s say myfile.zip) and setup a File Presenter for it // doing this we preserve access to the file until it‘s actually downloaded - let destinationFilePresenter = try SandboxFilePresenter(url: destinationURL, consumeUnbalancedStartAccessingResource: true, logger: log) { url in + let destinationFilePresenter = try BookmarkFilePresenter(url: destinationURL, consumeUnbalancedStartAccessingResource: true, logger: log) { url in try fm.createFile(atPath: url.path, contents: nil) ? url : { throw CocoaError(.fileWriteNoPermission, userInfo: [NSFilePathErrorKey: url.path]) }() @@ -353,45 +361,76 @@ final class WebKitDownloadTask: NSObject, ProgressReporting, @unchecked Sendable // 2. mark the file as hidden until it‘s downloaded to not to confuse user // and prevent from unintentional opening of the empty file - var resourceValues = URLResourceValues() - resourceValues.isHidden = true - try destinationURL.setResourceValues(resourceValues) - os_log(.debug, log: log, "🧙‍♂️ \"\(destinationURL.path)\" hidden, moving temp file from \"\(tempURL.path)\" to \"\(duckloadURL.path)\"") + try destinationURL.setFileHidden(true) + os_log(.debug, log: log, "🧙‍♂️ \"\(destinationURL.path)\" hidden") - // 3. then we move the temporary download file to the destination directory (myfile.zip.duckload) + // 3. then we move the temporary download file to the destination directory (myfile.duckload) // this is doable in sandboxed builds by using “Related Items” i.e. using a file URL with an extra // `.duckload` extension appended and “Primary Item” pointing to the sandbox-accessible destination URL // the `.duckload` document type is registered in the Info.plist with `NSIsRelatedItemType` flag // // - after the file is downloaded we‘ll replace the destination file with the `.duckload` file if fm.fileExists(atPath: duckloadURL.path) { - // remove the `.duckload` item if already exists + // `.duckload` already exists do { - try FilePresenter(url: duckloadURL, primaryItemURL: destinationURL).coordinateWrite(with: .forDeleting) { duckloadURL in - try fm.removeItem(at: duckloadURL) - } + try chooseAlternativeDuckloadFileNameOrRemove(&duckloadURL, destinationURL: destinationURL) } catch { // that‘s ok, we‘ll keep using the original temp file - os_log(.error, log: log, "❗️ could not remove \"\(duckloadURL.path)\" \(error)") + os_log(.error, log: log, "❗️ can‘t resolve duckload file exists: \"\(duckloadURL.path)\": \(error)") + duckloadURL = tempURL } } - // now move the temp file to `.duckload` instantiating a File Presenter with it - let tempFilePresenter = try SandboxFilePresenter(url: duckloadURL, primaryItemURL: destinationURL, logger: log) { [log] duckloadURL in - do { - try fm.moveItem(at: tempURL, to: duckloadURL) - } catch { - // fallback: move failed, keep the temp file in the original location - os_log(.error, log: log, "🙁 fallback with \(error), will use \(tempURL.path)") - Pixel.fire(.debug(event: .fileAccessRelatedItemFailed, error: error)) - return tempURL + + let tempFilePresenter = if duckloadURL == tempURL { + // we won‘t use a `.duckload` file for this download, the file will be left in the temp location instead + try BookmarkFilePresenter(url: duckloadURL, logger: log) + } else { + // now move the temp file to `.duckload` instantiating a File Presenter with it + try BookmarkFilePresenter(url: duckloadURL, primaryItemURL: destinationURL, logger: log) { [log] duckloadURL in + do { + try fm.moveItem(at: tempURL, to: duckloadURL) + return duckloadURL + } catch { + // fallback: move failed, keep the temp file in the original location + os_log(.error, log: log, "🙁 fallback with \(error), will use \(tempURL.path)") + Pixel.fire(.debug(event: .fileAccessRelatedItemFailed, error: error)) + return tempURL + } } - return duckloadURL } os_log(.debug, log: log, "🧙‍♂️ \"\(duckloadURL.path)\" (\"\(tempFilePresenter.url?.path ?? "")\") ready") return (tempFile: tempFilePresenter, destinationFile: destinationFilePresenter) } + private func chooseAlternativeDuckloadFileNameOrRemove(_ duckloadURL: inout URL, destinationURL: URL) throws { + let fm = FileManager() + // are we using the `.duckload` file for some other download (with different extension)? + if NSFileCoordinator.filePresenters.first(where: { $0.presentedItemURL?.resolvingSymlinksInPath() == duckloadURL.resolvingSymlinksInPath() }) != nil { + // if the downloads directory is writable without extra permission – try choosing another `.duckload` filename + if fm.isWritableFile(atPath: duckloadURL.deletingLastPathComponent().path) { + // append `.duckload` to the destination file name with extension + let destinationPathExtension = destinationURL.pathExtension + let pathExtension = destinationPathExtension.isEmpty ? Self.downloadExtension : destinationPathExtension + "." + Self.downloadExtension + duckloadURL = duckloadURL.deletingPathExtension().appendingPathExtension(pathExtension) + + // choose non-existent path + duckloadURL = try fm.withNonExistentUrl(for: duckloadURL, incrementingIndexIfExistsUpTo: 1000, pathExtension: pathExtension) { url in + try Data().write(to: url) + return url + } + } else { + // continue keeping the temp file in the temp dir + throw CocoaError(.fileWriteFileExists) + } + } + + os_log(.debug, log: log, "removing temp file \"\(duckloadURL.path)\"") + try FilePresenter(url: duckloadURL, primaryItemURL: destinationURL).coordinateWrite(with: .forDeleting) { duckloadURL in + try fm.removeItem(at: duckloadURL) + } + } + private nonisolated func reuseFilePresenters(tempFile: FilePresenter, destination: FilePresenter, tempURL: URL) async throws -> (tempFile: FilePresenter, destinationFile: FilePresenter) { // if the download is “resumed” as a new download (replacing the destination file) - // use the existing `.duckload` file and move the temp file in its place @@ -587,6 +626,7 @@ extension WebKitDownloadTask: WKDownloadDelegate { return nil } + self.selectedDestinationURL = destinationURL return await prepareChosenDestinationURL(destinationURL, fileType: suggestedFileType, cleanupStyle: cleanupStyle) } @@ -697,20 +737,7 @@ extension WebKitDownloadTask { override var description: String { guard Thread.isMainThread else { #if DEBUG - os_log(""" - - - ------------------------------------------------------------------------------------------------------ - BREAK: - ------------------------------------------------------------------------------------------------------ - - ❗️accessing WebKitDownloadTask.description from non-main thread - - Hit Continue (^⌘Y) to continue program execution - ------------------------------------------------------------------------------------------------------ - - """, type: .fault) - raise(SIGINT) + breakByRaisingSigInt("❗️accessing WebKitDownloadTask.description from non-main thread") #endif return "" } diff --git a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift index 5e9c9e14c9..70cfda3f2d 100644 --- a/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift +++ b/DuckDuckGo/FileDownload/Services/DownloadListCoordinator.swift @@ -152,9 +152,9 @@ final class DownloadListCoordinator { // locate destination file let destinationPresenterResult = Result { if let destinationFileBookmarkData = item.destinationFileBookmarkData { - try SandboxFilePresenter(fileBookmarkData: destinationFileBookmarkData, logger: log) + try BookmarkFilePresenter(fileBookmarkData: destinationFileBookmarkData, logger: log) } else if let destinationURL = item.destinationURL { - try SandboxFilePresenter(url: destinationURL, logger: log) + try BookmarkFilePresenter(url: destinationURL, logger: log) } else { nil } @@ -163,9 +163,9 @@ final class DownloadListCoordinator { // locate temp download file var tempFilePresenterResult = Result { if let tempFileBookmarkData = item.tempFileBookmarkData { - try SandboxFilePresenter(fileBookmarkData: tempFileBookmarkData, logger: log) + try BookmarkFilePresenter(fileBookmarkData: tempFileBookmarkData, logger: log) } else if let tempURL = item.tempURL { - try SandboxFilePresenter(url: tempURL, logger: log) + try BookmarkFilePresenter(url: tempURL, logger: log) } else { nil } @@ -223,10 +223,10 @@ final class DownloadListCoordinator { case .downloading(destination: let destination, tempFile: let tempFile): self.addItemIfNeededAndSubscribe(to: (destination, tempFile), for: item) case .downloaded(let destination): - let updatedItem = self.downloadTask(task, withId: item.identifier, completedWith: .finished) + let updatedItem = self.downloadTask(task, withOriginalItem: item, completedWith: .finished) self.subscribeToPresenters((destination: destination, tempFile: nil), of: updatedItem ?? item) case .failed(destination: let destination, tempFile: let tempFile, resumeData: _, error: let error): - let updatedItem = self.downloadTask(task, withId: item.identifier, completedWith: .failure(error)) + let updatedItem = self.downloadTask(task, withOriginalItem: item, completedWith: .failure(error)) self.subscribeToPresenters((destination: destination, tempFile: tempFile), of: updatedItem ?? item) } } @@ -250,7 +250,7 @@ final class DownloadListCoordinator { Publishers.CombineLatest( presenters.destination?.urlPublisher ?? Just(nil).eraseToAnyPublisher(), - (presenters.destination as? SandboxFilePresenter)?.fileBookmarkDataPublisher ?? Just(nil).eraseToAnyPublisher() + (presenters.destination as? BookmarkFilePresenter)?.fileBookmarkDataPublisher ?? Just(nil).eraseToAnyPublisher() ) .scan((oldURL: nil, newURL: nil, fileBookmarkData: nil)) { (oldURL: $0.newURL, newURL: $1.0, fileBookmarkData: $1.1) } .sink { [weak self] oldURL, newURL, fileBookmarkData in @@ -279,7 +279,7 @@ final class DownloadListCoordinator { Publishers.CombineLatest( presenters.tempFile?.urlPublisher ?? Just(nil).eraseToAnyPublisher(), - (presenters.tempFile as? SandboxFilePresenter)?.fileBookmarkDataPublisher ?? Just(nil).eraseToAnyPublisher() + (presenters.tempFile as? BookmarkFilePresenter)?.fileBookmarkDataPublisher ?? Just(nil).eraseToAnyPublisher() ) .scan((oldURL: nil, newURL: nil, fileBookmarkData: nil)) { (oldURL: $0.newURL, newURL: $1.0, fileBookmarkData: $1.1) } .sink { [weak self] oldURL, newURL, fileBookmarkData in @@ -341,19 +341,25 @@ final class DownloadListCoordinator { } @MainActor - private func downloadTask(_ task: WebKitDownloadTask, withId identifier: UUID, completedWith result: Subscribers.Completion) -> DownloadListItem? { - os_log(.debug, log: log, "coordinator: task did finish \(identifier) \(task) with .\(result)") + private func downloadTask(_ task: WebKitDownloadTask, withOriginalItem initialItem: DownloadListItem, completedWith result: Subscribers.Completion) -> DownloadListItem? { + os_log(.debug, log: log, "coordinator: task did finish \(initialItem.identifier) \(task) with .\(result)") self.downloadTaskCancellables[task] = nil - // item will be really updated (completed) only if it was added before in `addItemOrUpdateFilePresenter` (when state switched to .downloading) - // if it has failed without starting - it won‘t be added or updated here - return updateItem(withId: identifier) { item in + return updateItem(withId: initialItem.identifier) { item in if item?.isBurner ?? false { item = nil return } + if item == nil, + case .failure(let failure) = result, !failure.isCancelled, + let fileName = task.selectedDestinationURL?.lastPathComponent { + // add instantly failed downloads to the list (not user-cancelled) + item = initialItem + item?.fileName = fileName + } + item?.progress = nil if case .failure(let error) = result { item?.error = error diff --git a/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift b/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift index ce6962e4bf..e8e435c447 100644 --- a/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift +++ b/DuckDuckGo/Preferences/Model/DownloadsPreferences.swift @@ -16,6 +16,7 @@ // limitations under the License. // +import Common import Foundation protocol DownloadsPreferencesPersistor { @@ -65,17 +66,19 @@ final class DownloadsPreferences: ObservableObject { static let shared = DownloadsPreferences(persistor: DownloadsPreferencesUserDefaultsPersistor()) - private func validatedDownloadLocation(_ location: String?) -> URL? { - if let selectedLocation = location, - let selectedLocationURL = URL(string: selectedLocation), - Self.isDownloadLocationValid(selectedLocationURL) { - return selectedLocationURL + private func validatedDownloadLocation(_ selectedLocation: URL?) -> URL? { + if let selectedLocation, Self.isDownloadLocationValid(selectedLocation) { + return selectedLocation } return nil } var effectiveDownloadLocation: URL? { - if let selectedLocationURL = alwaysRequestDownloadLocation ? validatedDownloadLocation(persistor.lastUsedCustomDownloadLocation) : validatedDownloadLocation(persistor.selectedDownloadLocation) { + if alwaysRequestDownloadLocation { + if let lastUsedCustomDownloadLocation = validatedDownloadLocation(persistor.lastUsedCustomDownloadLocation.flatMap(URL.init(string:))) { + return lastUsedCustomDownloadLocation + } + } else if let selectedLocationURL = validatedDownloadLocation(selectedDownloadLocation) { return selectedLocationURL } return Self.defaultDownloadLocation() @@ -94,37 +97,54 @@ final class DownloadsPreferences: ObservableObject { defer { objectWillChange.send() } - guard let newDownloadLocation = newValue else { - persistor.lastUsedCustomDownloadLocation = nil - return - } - if Self.isDownloadLocationValid(newDownloadLocation) { - persistor.lastUsedCustomDownloadLocation = newDownloadLocation.absoluteString - } + persistor.lastUsedCustomDownloadLocation = newValue?.absoluteString } } + private var selectedDownloadLocationController: SecurityScopedFileURLController? + var selectedDownloadLocation: URL? { get { - persistor.selectedDownloadLocation?.url + if let selectedDownloadLocation = selectedDownloadLocationController?.url { + return selectedDownloadLocation + } +#if APPSTORE + var isStale = false + if let bookmarkData = persistor.selectedDownloadLocation.flatMap({ Data(base64Encoded: $0) }), + let url = try? URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) { + if isStale { + setSelectedDownloadLocation(url) // update bookmark data and selectedDownloadLocationController + } else { + selectedDownloadLocationController = SecurityScopedFileURLController(url: url, logger: OSLog.downloads) + } + return url + } +#endif + guard let url = persistor.selectedDownloadLocation.flatMap(URL.init(string:)), + url.isFileURL else { return nil } + return url.resolvingSymlinksInPath() } - set { defer { objectWillChange.send() } - guard let newDownloadLocation = newValue else { - persistor.selectedDownloadLocation = nil - return - } - if Self.isDownloadLocationValid(newDownloadLocation) { - persistor.selectedDownloadLocation = newDownloadLocation.absoluteString - } + setSelectedDownloadLocation(validatedDownloadLocation(newValue)) } } + private func setSelectedDownloadLocation(_ url: URL?) { + selectedDownloadLocationController = url.map { SecurityScopedFileURLController(url: $0, logger: OSLog.downloads) } + let locationString: String? +#if APPSTORE + locationString = (try? url?.bookmarkData(options: .withSecurityScope).base64EncodedString()) ?? url?.absoluteString +#else + locationString = url?.absoluteString +#endif + persistor.selectedDownloadLocation = locationString + } + var alwaysRequestDownloadLocation: Bool { get { persistor.alwaysRequestDownloadLocation diff --git a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift index 521893ba2e..dc4da53438 100644 --- a/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift +++ b/DuckDuckGo/Preferences/View/PreferencesGeneralView.swift @@ -108,15 +108,15 @@ extension Preferences { // MARK: Location PreferencePaneSubSection { Text(UserText.downloadsLocation).bold() + HStack { NSPathControlView(url: downloadsModel.selectedDownloadLocation) -#if !APPSTORE Button(UserText.downloadsChangeDirectory) { downloadsModel.presentDownloadDirectoryPanel() } -#endif } .disabled(downloadsModel.alwaysRequestDownloadLocation) + ToggleMenuItem(UserText.downloadsAlwaysAsk, isOn: $downloadsModel.alwaysRequestDownloadLocation) } diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index 539d7007ea..d43aa95fd9 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -974,13 +974,28 @@ extension BrowserTabViewController: TabDelegate { suggestedFilename: request.parameters.suggestedFilename, directoryURL: directoryURL) - savePanel.beginSheetModal(for: window) { [weak request] response in + savePanel.beginSheetModal(for: window) { [weak request, weak self] response in switch response { case .abort: // panel not closed by user but by a tab switching return case .OK: - guard let url = savePanel.url else { fallthrough } + guard let self, + let window = view.window, + let url = savePanel.url else { fallthrough } + + do { + // validate selected URL is writable + try FileManager.default.checkWritability(url) + } catch { + // hide the save panel + self.activeUserDialogCancellable = nil + NSAlert(error: error).beginSheetModal(for: window) { [weak self] _ in + guard let self, let request else { return } + self.activeUserDialogCancellable = showSavePanel(with: request) + } + return + } request?.submit( (url, savePanel.selectedFileType) ) default: request?.submit(nil) diff --git a/DuckDuckGo/Tab/View/WebView.swift b/DuckDuckGo/Tab/View/WebView.swift index bc578f6d19..249356909b 100644 --- a/DuckDuckGo/Tab/View/WebView.swift +++ b/DuckDuckGo/Tab/View/WebView.swift @@ -30,6 +30,7 @@ protocol WebViewInteractionEventsDelegate: AnyObject { func webView(_ webView: WebView, scrollWheel event: NSEvent) } +@objc(DuckDuckGo_WebView) final class WebView: WKWebView { weak var contextMenuDelegate: WebViewContextMenuDelegate? diff --git a/IntegrationTests/Downloads/DownloadsIntegrationTests.swift b/IntegrationTests/Downloads/DownloadsIntegrationTests.swift index 35e2024c05..d108921c11 100644 --- a/IntegrationTests/Downloads/DownloadsIntegrationTests.swift +++ b/IntegrationTests/Downloads/DownloadsIntegrationTests.swift @@ -70,9 +70,9 @@ class DownloadsIntegrationTests: XCTestCase { @MainActor func testWhenShouldDownloadResponse_downloadStarts() async throws { - let persistor = DownloadsPreferencesUserDefaultsPersistor() - persistor.alwaysRequestDownloadLocation = false - persistor.selectedDownloadLocation = FileManager.default.temporaryDirectory.absoluteString + let preferences = DownloadsPreferences.shared + preferences.alwaysRequestDownloadLocation = false + preferences.selectedDownloadLocation = FileManager.default.temporaryDirectory let downloadTaskFuture = FileDownloadManager.shared.downloadsPublisher.timeout(5).first().promise() let suffix = Int.random(in: 0.. URL { @@ -95,15 +94,17 @@ final class FilePresenterTests: XCTestCase { return app } - private func terminateApp(timeout: TimeInterval = 1) async { - let eTerminated = runningApp != nil ? expectation(description: "terminated") : nil + private func terminateApp(timeout: TimeInterval = 5, expectation: XCTestExpectation = XCTestExpectation(description: "terminated")) async { + if runningApp == nil { + expectation.fulfill() + } let c = runningApp?.publisher(for: \.isTerminated).filter { $0 }.sink { _ in - eTerminated?.fulfill() + expectation.fulfill() } post(.terminate) runningApp?.forceTerminate() - await fulfillment(of: eTerminated.map { [$0] } ?? [], timeout: timeout) + await fulfillment(of: [expectation], timeout: timeout) withExtendedLifetime(c) {} } @@ -111,7 +112,7 @@ final class FilePresenterTests: XCTestCase { DistributedNotificationCenter.default().post(name: .init(name.rawValue), object: object) } - private func fileReadPromise(timeout: TimeInterval = 5) -> Future { + private func fileReadPromise(timeout: TimeInterval = 5, file: StaticString = #file, line: UInt = #line) -> Future { Future { [unowned self] fulfill in onFileRead = { result in fulfill(.success(result)) @@ -124,13 +125,14 @@ final class FilePresenterTests: XCTestCase { self.onError = nil } } - .timeout(timeout) + .timeout(timeout, file: file, line: line) .first() .promise() } // MARK: - Test sandboxed file access #if APPSTORE && !CI + func testTool_run() async throws { // 1. make non-sandbox file let nonSandboxUrl = try makeNonSandboxFile() @@ -177,7 +179,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) // 4. read the file @@ -188,7 +190,7 @@ final class FilePresenterTests: XCTestCase { XCTAssertEqual(result.data, testData.utf8String()) XCTAssertEqual(result.bookmark, bookmark) - // 5. close SandboxFilePresenter + // 5. close BookmarkFilePresenter let e = expectation(description: "access stopped") let c = DistributedNotificationCenter.default().publisher(for: SandboxTestNotification.stopAccessingSecurityScopedResourceCalled.name).sink { _ in e.fulfill() @@ -220,7 +222,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() post(.openFile, with: nonSandboxUrl.path) @@ -290,7 +292,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() @@ -316,7 +318,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() post(.openFile, with: nonSandboxUrl.path) @@ -355,7 +357,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() post(.openFile, with: nonSandboxUrl.path) @@ -374,7 +376,7 @@ final class FilePresenterTests: XCTestCase { } await fulfillment(of: [e], timeout: 5) - // 5. close SandboxFilePresenter + // 5. close BookmarkFilePresenter let eStopped = expectation(description: "access stopped") let c2 = DistributedNotificationCenter.default().publisher(for: SandboxTestNotification.stopAccessingSecurityScopedResourceCalled.name).sink { _ in eStopped.fulfill() @@ -406,13 +408,13 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() post(.openFile, with: nonSandboxUrl.path) _=try await fileReadPromise.value - // 4. close SandboxFilePresenter + // 4. close BookmarkFilePresenter let eStopped = expectation(description: "access stopped") let c2 = DistributedNotificationCenter.default().publisher(for: SandboxTestNotification.stopAccessingSecurityScopedResourceCalled.name).sink { _ in eStopped.fulfill() @@ -456,7 +458,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() post(.openFile, with: nonSandboxUrl.path) @@ -503,7 +505,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) fileReadPromise = self.fileReadPromise() post(.openFile, with: nonSandboxUrl.path) @@ -543,7 +545,7 @@ final class FilePresenterTests: XCTestCase { await terminateApp() runningApp = try await runHelperApp() - // 3. open the bookmark with SandboxFilePresenter + // 3. open the bookmark with BookmarkFilePresenter post(.openBookmarkWithFilePresenter, with: bookmark1.base64EncodedString()) post(.openBookmarkWithFilePresenter, with: bookmark2.base64EncodedString()) fileReadPromise = self.fileReadPromise() @@ -571,7 +573,6 @@ final class FilePresenterTests: XCTestCase { // 5. close FilePresenter 1 (at the original URL) let e3 = expectation(description: "access stopped") let c2 = DistributedNotificationCenter.default().publisher(for: SandboxTestNotification.stopAccessingSecurityScopedResourceCalled.name).sink { n in - XCTAssertEqual(n.object as? String, nonSandboxUrl1.path) e3.fulfill() } post(.closeFilePresenter, with: nonSandboxUrl1.path) @@ -600,32 +601,6 @@ final class FilePresenterTests: XCTestCase { } } - func testWhenFilePresenterClosesFileOpenedByOS_fileAccessIsPreserved() async throws { - // 1. make non-sandbox file and open the file with the helper app - let nonSandboxUrl = try makeNonSandboxFile() - var fileReadPromise = self.fileReadPromise() - runningApp = try await runHelperApp(opening: nonSandboxUrl) - guard let bookmark = try await fileReadPromise.value.bookmark else { XCTFail("No bookmark"); return } - - // 2. open file presenter - post(.openBookmarkWithFilePresenter, with: bookmark.base64EncodedString()) - - // 3. close the file presenter - let e = expectation(description: "access stopped") - let c = DistributedNotificationCenter.default().publisher(for: SandboxTestNotification.stopAccessingSecurityScopedResourceCalled.name).sink { n in - XCTAssertEqual(n.object as? String, nonSandboxUrl.path) - e.fulfill() - } - post(.closeFilePresenter, with: nonSandboxUrl.path) - await fulfillment(of: [e], timeout: 1) - withExtendedLifetime(c) {} - - // 4. validate file can still be read - fileReadPromise = self.fileReadPromise() - post(.openFile, with: nonSandboxUrl.path) - let result = try await fileReadPromise.value - XCTAssertEqual(result.path, nonSandboxUrl.path) - } #endif // MARK: - Test non-sandboxed file access @@ -633,10 +608,10 @@ final class FilePresenterTests: XCTestCase { func testWhenSandboxFilePresenterIsOpen_itCanReadFile_accessIsNotStoppedWhenClosed_noSandbox() async throws { // 1. make non-sandbox file; create bookmark let nonSandboxUrl = try makeNonSandboxFile() - guard let bookmarkData = try SandboxFilePresenter(url: nonSandboxUrl).fileBookmarkData else { XCTFail("No bookmark"); return } + guard let bookmarkData = try BookmarkFilePresenter(url: nonSandboxUrl).fileBookmarkData else { XCTFail("No bookmark"); return } - // 2. open the bookmark with SandboxFilePresenter - var filePresenter: SandboxFilePresenter! = try SandboxFilePresenter(fileBookmarkData: bookmarkData) + // 2. open the bookmark with BookmarkFilePresenter + var filePresenter: BookmarkFilePresenter! = try BookmarkFilePresenter(fileBookmarkData: bookmarkData) // 3. validate var publishedUrl: URL? @@ -656,7 +631,7 @@ final class FilePresenterTests: XCTestCase { func testWhenFileIsRenamed_urlIsUpdated_noSandbox() async throws { // 1. make non-sandbox file let nonSandboxUrl = try makeNonSandboxFile() - let filePresenter = try SandboxFilePresenter(url: nonSandboxUrl) + let filePresenter = try BookmarkFilePresenter(url: nonSandboxUrl) // 4. rename the file let newUrl = nonSandboxUrl.deletingPathExtension().appendingPathExtension("1.txt") @@ -689,7 +664,7 @@ final class FilePresenterTests: XCTestCase { func testWhenFileIsRemoved_removalIsDetected_noSandbox() async throws { // 1. make non-sandbox file let nonSandboxUrl = try makeNonSandboxFile() - let filePresenter = try SandboxFilePresenter(url: nonSandboxUrl) + let filePresenter = try BookmarkFilePresenter(url: nonSandboxUrl) // 2. remove the file let e1 = expectation(description: "file presenter: file removed") @@ -716,8 +691,8 @@ final class FilePresenterTests: XCTestCase { let bookmarkData1 = try nonSandboxUrl1.bookmarkData(options: .withSecurityScope) let nonSandboxUrl2 = try makeNonSandboxFile() let bookmarkData2 = try nonSandboxUrl2.bookmarkData(options: .withSecurityScope) - let filePresenter1 = try SandboxFilePresenter(fileBookmarkData: bookmarkData1) - let filePresenter2 = try SandboxFilePresenter(fileBookmarkData: bookmarkData2) + let filePresenter1 = try BookmarkFilePresenter(fileBookmarkData: bookmarkData1) + let filePresenter2 = try BookmarkFilePresenter(fileBookmarkData: bookmarkData2) // 2. cross-rename the files let tempUrl = nonSandboxUrl1.appendingPathExtension("tmp") diff --git a/UnitTests/Preferences/DownloadsPreferencesTests.swift b/UnitTests/Preferences/DownloadsPreferencesTests.swift index 3213d7c0af..7c50d88ff5 100644 --- a/UnitTests/Preferences/DownloadsPreferencesTests.swift +++ b/UnitTests/Preferences/DownloadsPreferencesTests.swift @@ -142,7 +142,7 @@ class DownloadsPreferencesTests: XCTestCase { preferences.selectedDownloadLocation = invalidDownloadLocationURL - XCTAssertEqual(preferences.effectiveDownloadLocation, testDirectory) + XCTAssertEqual(preferences.effectiveDownloadLocation, DownloadsPreferences.defaultDownloadLocation()) } func testWhenGettingSelectedDownloadLocationAndSelectedLocationIsInaccessibleThenDefaultDownloadLocationIsReturned() { @@ -213,18 +213,15 @@ class DownloadsPreferencesTests: XCTestCase { XCTAssertNil(preferences.lastUsedCustomDownloadLocation) } - func testWhenInvalidLastUsedCustomDownloadLocationIsSet_oldValueIsPreserved() { + func testWhenInvalidLastUsedCustomDownloadLocationIsSet_lastUsedCustomLocationIsNil() { let testDirectory = createTemporaryTestDirectory() let persistor = DownloadsPreferencesPersistorMock(selectedDownloadLocation: nil) let preferences = DownloadsPreferences(persistor: persistor) - let valuesBeforeChange = persistor.values() preferences.lastUsedCustomDownloadLocation = testDirectory preferences.lastUsedCustomDownloadLocation = testDirectory.appendingPathComponent("non-existent-dir") - let valuesAfterChange = persistor.values() - XCTAssertEqual(valuesBeforeChange.difference(from: valuesAfterChange), ["\(\DownloadsPreferencesPersistorMock.lastUsedCustomDownloadLocation)".pathExtension]) - XCTAssertEqual(preferences.lastUsedCustomDownloadLocation, testDirectory) + XCTAssertNil(preferences.lastUsedCustomDownloadLocation) } func testWhenLastUsedCustomDownloadLocationIsReset_nilIsReturned() { diff --git a/sandbox-test-tool/SandboxTestTool.swift b/sandbox-test-tool/SandboxTestTool.swift index 2a800cfa18..1234426615 100644 --- a/sandbox-test-tool/SandboxTestTool.swift +++ b/sandbox-test-tool/SandboxTestTool.swift @@ -55,7 +55,11 @@ extension FileLogger: FilePresenterLogger { final class FileLogger { static let shared = FileLogger() - private init() {} + private init() { + if !FileManager.default.fileExists(atPath: fileURL.path) { + FileManager.default.createFile(atPath: fileURL.path, contents: nil) + } + } private let pid = ProcessInfo().processIdentifier @@ -176,7 +180,7 @@ final class SandboxTestToolAppDelegate: NSObject, NSApplicationDelegate { return } do { - let filePresenter = try SandboxFilePresenter(fileBookmarkData: bookmark, logger: logger) + let filePresenter = try BookmarkFilePresenter(fileBookmarkData: bookmark, logger: logger) guard let url = filePresenter.url else { throw NSError(domain: "SandboxTestTool", code: -1, userInfo: [NSLocalizedDescriptionKey: "FilePresenter URL is nil"]) } filePresenter.urlPublisher.dropFirst().sink { [unowned self] url in @@ -188,7 +192,7 @@ final class SandboxTestToolAppDelegate: NSObject, NSApplicationDelegate { self.filePresenters[url] = filePresenter logger.log("📗 openBookmarkWithFilePresenter done: \"\(filePresenter.url?.path ?? "")\"") } catch { - post(.error, with: error.encoded("could not open SandboxFilePresenter")) + post(.error, with: error.encoded("could not open BookmarkFilePresenter")) } } @@ -198,7 +202,6 @@ final class SandboxTestToolAppDelegate: NSObject, NSApplicationDelegate { return } logger.log("🌂 closeFilePresenter for \(path)") - let url = URL(fileURLWithPath: path) filePresenterCancellables[url] = nil filePresenters[url] = nil @@ -230,3 +233,31 @@ private extension Error { return String(data: json!, encoding: .utf8)! } } + +extension NSURL { + + private static var stopAccessingSecurityScopedResourceCallback: ((URL) -> Void)? + + private static let originalStopAccessingSecurityScopedResource = { + class_getInstanceMethod(NSURL.self, #selector(NSURL.stopAccessingSecurityScopedResource))! + }() + private static let swizzledStopAccessingSecurityScopedResource = { + class_getInstanceMethod(NSURL.self, #selector(NSURL.test_tool_stopAccessingSecurityScopedResource))! + }() + private static let swizzleStopAccessingSecurityScopedResourceOnce: Void = { + method_exchangeImplementations(originalStopAccessingSecurityScopedResource, swizzledStopAccessingSecurityScopedResource) + }() + + static func swizzleStopAccessingSecurityScopedResource(with stopAccessingSecurityScopedResourceCallback: ((URL) -> Void)?) { + _=swizzleStopAccessingSecurityScopedResourceOnce + self.stopAccessingSecurityScopedResourceCallback = stopAccessingSecurityScopedResourceCallback + } + + @objc private dynamic func test_tool_stopAccessingSecurityScopedResource() { + if let stopAccessingSecurityScopedResourceCallback = Self.stopAccessingSecurityScopedResourceCallback { + stopAccessingSecurityScopedResourceCallback(self as URL) + } + self.test_tool_stopAccessingSecurityScopedResource() // call original + } + +} From 404b0b04e9b3edf89337e968255dfb0ca7bb267d Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 15:04:53 +0500 Subject: [PATCH 26/40] Add supported document types (#2581) Task/Issue URL: https://app.asana.com/0/1177771139624306/1201791966665400/f --- .../Common/Extensions/BundleExtension.swift | 10 + .../Common/Extensions/NSAlertExtension.swift | 9 - DuckDuckGo/Common/Localizables/UserText.swift | 2 - .../SecurityScopedFileURLController.swift | 3 + DuckDuckGo/Info.plist | 476 ++++++++++++++++-- .../View/AddressBarTextField.swift | 8 +- DuckDuckGo/Tab/Model/Tab.swift | 15 +- .../TabExtensions/DownloadsTabExtension.swift | 11 +- 8 files changed, 478 insertions(+), 56 deletions(-) diff --git a/DuckDuckGo/Common/Extensions/BundleExtension.swift b/DuckDuckGo/Common/Extensions/BundleExtension.swift index b3c7a629f5..28133debd8 100644 --- a/DuckDuckGo/Common/Extensions/BundleExtension.swift +++ b/DuckDuckGo/Common/Extensions/BundleExtension.swift @@ -26,6 +26,8 @@ extension Bundle { static let buildNumber = kCFBundleVersionKey as String static let versionNumber = "CFBundleShortVersionString" static let displayName = "CFBundleDisplayName" + static let documentTypes = "CFBundleDocumentTypes" + static let typeExtensions = "CFBundleTypeExtensions" static let vpnMenuAgentBundleId = "AGENT_BUNDLE_ID" static let vpnMenuAgentProductName = "AGENT_PRODUCT_NAME" @@ -115,6 +117,14 @@ extension Bundle { return path.hasPrefix(applicationsPath) } + var documentTypes: [[String: Any]] { + infoDictionary?[Keys.documentTypes] as? [[String: Any]] ?? [] + } + + var fileTypeExtensions: Set { + documentTypes.reduce(into: []) { $0.formUnion($1[Keys.typeExtensions] as? [String] ?? []) } + } + } enum BundleGroup { diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 554ce896e5..a1f71cdeb4 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -137,15 +137,6 @@ extension NSAlert { return alert } - static func noAccessToSelectedFolder() -> NSAlert { - let alert = NSAlert() - alert.messageText = UserText.noAccessToSelectedFolderHeader - alert.informativeText = UserText.noAccessToSelectedFolder - alert.alertStyle = .warning - alert.addButton(withTitle: UserText.cancel) - return alert - } - static func disableEmailProtection() -> NSAlert { let alert = NSAlert() alert.messageText = UserText.disableEmailProtectionTitle diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 4033037606..00a327d353 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -976,8 +976,6 @@ struct UserText { } } - static let noAccessToSelectedFolderHeader = NSLocalizedString("no.access.to.selected.folder.header", value: "DuckDuckGo needs permission to access selected folder", comment: "Header of the alert dialog informing user about failed download") - static let noAccessToSelectedFolder = NSLocalizedString("no.access.to.selected.folder", value: "Grant access to the location of download.", comment: "Alert presented to user if the app doesn't have rights to access selected folder") static let cannotOpenFileAlertHeader = NSLocalizedString("cannot.open.file.alert.header", value: "Cannot Open File", comment: "Header of the alert dialog informing user it is not possible to open the file") static let cannotOpenFileAlertInformative = NSLocalizedString("cannot.open.file.alert.informative", value: "The App Store version of DuckDuckGo can only access local files if you drag-and-drop them into a browser window.\n\n To navigate local files using the address bar, please download DuckDuckGo directly from https://duckduckgo.com/mac.", comment: "Informative of the alert dialog informing user it is not possible to open the file") diff --git a/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift b/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift index 7cdc7eecdf..bec94e02ac 100644 --- a/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift +++ b/DuckDuckGo/FileDownload/Model/SecurityScopedFileURLController.swift @@ -152,6 +152,9 @@ extension NSURL { func hash(into hasher: inout Hasher) { hasher.combine(ObjectIdentifier(url)) } + static func == (lhs: Self, rhs: Self) -> Bool { + lhs.url === rhs.url + } } static var activeSecurityScopedUrlUsages: Set = [] #endif diff --git a/DuckDuckGo/Info.plist b/DuckDuckGo/Info.plist index 45285233da..095900fb99 100644 --- a/DuckDuckGo/Info.plist +++ b/DuckDuckGo/Info.plist @@ -9,44 +9,444 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDocumentTypes - - - CFBundleTypeExtensions - - html - htm - shtml - xht - xhtml - - CFBundleTypeIconFile - document.icns - CFBundleTypeName - HTML Document - CFBundleTypeOSTypes - - HTML - - CFBundleTypeRole - Viewer - LSHandlerRank - Default - - - CFBundleTypeExtensions - - duckload - - CFBundleTypeName - Incomplete download - CFBundleTypeRole - Editor - NSIsRelatedItemType - - LSHandlerRank - Owner - - + + + CFBundleTypeExtensions + + html + htm + shtml + xht + xhtml + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + HTML Document + CFBundleTypeOSTypes + + HTML + + CFBundleTypeRole + Viewer + LSHandlerRank + Default + + + CFBundleTypeExtensions + + txt + text + log + + CFBundleTypeMIMETypes + + text/plain + + LSItemContentTypes + + public.text + + CFBundleTypeOSTypes + + TEXT + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + Text document + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + webarchive + + CFBundleTypeMIMETypes + + application/x-webarchive + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + Web archive + CFBundleTypeRole + Viewer + LSHandlerRank + Default + + + CFBundleTypeExtensions + + duckload + + CFBundleTypeName + Incomplete download + CFBundleTypeRole + Editor + NSIsRelatedItemType + + LSHandlerRank + Owner + + + CFBundleTypeExtensions + + pdf + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + PDF Document + CFBundleTypeMIMETypes + + application/pdf + + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + png + + CFBundleTypeMIMETypes + + image/png + + LSItemContentTypes + + public.png + + CFBundleTypeOSTypes + + PNGf + + CFBundleTypeIconFile + document.icns + CFBundleTypeName + PNG image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + jpg + jpeg + jp2 + jpeg2 + + CFBundleTypeMIMETypes + + image/jpeg + image/jp2 + image/jpeg2000 + + CFBundleTypeOSTypes + + JPEG + jp2 + + LSItemContentTypes + + public.jpeg + public.jpeg-2000 + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + JPEG image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + gif + giff + + CFBundleTypeMIMETypes + + image/gif + + CFBundleTypeOSTypes + + GIFf + + LSItemContentTypes + + com.compuserve.gif + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + GIF image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + svg + + CFBundleTypeMIMETypes + + image/svg+xml + + LSItemContentTypes + + public.svg-image + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + SVG image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + tiff + tif + + CFBundleTypeMIMETypes + + image/tiff + + CFBundleTypeOSTypes + + TIFF + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + TIFF image + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + ico + + CFBundleTypeMIMETypes + + image/x-icon + image/icon + image/ico + + CFBundleTypeOSTypes + + ICO + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + Windows icon + CFBundleTypeRole + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + bmp + + CFBundleTypeMIMETypes + + image/bmp + image/x-bitmap + image/x-bmp + image/x-ms-bitmap + image/x-ms-bmp + + LSItemContentTypes + + com.microsoft.bmp + + CFBundleTypeName + BMP Image + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + LSRoleHandlerScheme + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + xml + rss + atom + + CFBundleTypeMIMETypes + + text/xml + application/xml + application/rss+xml + application/atom+xml + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + XML document + CFBundleTypeRole + Viewer + LSHandlerRank + Default + + + CFBundleTypeExtensions + + json + + CFBundleTypeMIMETypes + + text/json + application/json + + LSItemContentTypes + + public.json + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + JSON document + CFBundleTypeRole + Viewer + LSHandlerRank + Default + + + CFBundleTypeExtensions + + flac + m4a + mp3 + mpg + wav + aac + amr + mp4 + mpeg + + CFBundleTypeMIMETypes + + audio/flac + audio/x-flac + audio/m4a + audio/mp4 + audio/mp3 + audio/mpeg + audio/mpeg3 + audio/qcp + audio/qcelp + audio/vnd.qcelp + audio/vnd.qcp + audio/vnd.wave + audio/x-wav + audio/x-aac + audio/aac + audio/x-amr + audio/amr + audio/x-m4a + audio/x-mp3 + audio/x-mp4 + audio/x-mpeg + audio/x-mpeg3 + audio/x-mpg + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + Audio File + LSRoleHandlerScheme + Viewer + LSHandlerRank + Alternate + + + CFBundleTypeExtensions + + 3gp + avi + m4v + mov + mp4 + + CFBundleTypeMIMETypes + + video/3gp + video/3gpp + video/avi + video/x-msvideo + video/x-m4v + video/mp4 + video/x-quicktime + video/quicktime + + LSItemContentTypes + + public.movie + + CFBundleTypeIconFile + document.icns + CFBundleTypeIconSystemGenerated + YES + CFBundleTypeName + Video File + LSRoleHandlerScheme + Viewer + LSHandlerRank + Alternate + + CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier diff --git a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift index 84fbb30576..b68ce5a6ee 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarTextField.swift @@ -332,9 +332,10 @@ final class AddressBarTextField: NSTextField { } #if APPSTORE - if providedUrl.isFileURL, let window = self.window { - let alert = NSAlert.cannotOpenFileAlert() - alert.beginSheetModal(for: window) { response in + if providedUrl.isFileURL, !providedUrl.isWritableLocation(), // is sandbox extension available for the file? + let window = self.window { + + NSAlert.cannotOpenFileAlert().beginSheetModal(for: window) { response in switch response { case .alertSecondButtonReturn: WindowControllersManager.shared.show(url: URL.ddgLearnMore, source: .ui, newTab: false) @@ -344,6 +345,7 @@ final class AddressBarTextField: NSTextField { return } } + return } #endif diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index bfa43c24e3..bac1e48b47 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -1063,8 +1063,11 @@ protocol NewWindowPolicyDecisionMaker { let source = content.source if url.isFileURL { + // WebKit won‘t load local page‘s external resouces even with `allowingReadAccessTo` provided + // this could be fixed using a custom scheme handler loading local resources in future. + let readAccessScopeURL = url return webView.navigator(distributedNavigationDelegate: navigationDelegate) - .loadFileURL(url, allowingReadAccessTo: URL(fileURLWithPath: "/"), withExpectedNavigationType: source.navigationType) + .loadFileURL(url, allowingReadAccessTo: readAccessScopeURL, withExpectedNavigationType: source.navigationType) } var request = URLRequest(url: url, cachePolicy: source.cachePolicy) @@ -1122,7 +1125,12 @@ protocol NewWindowPolicyDecisionMaker { // only restore session from interactionStateData passed to Tab.init guard case .loadCachedFromTabContent(let interactionStateData) = self.interactionState else { return false } - if let url = content.urlForWebView, url.isFileURL { + switch content.urlForWebView { + case .some(let url) where url.isFileURL: +#if APPSTORE + guard url.isWritableLocation() else { fallthrough } +#endif + // request file system access before restoration webView.navigator(distributedNavigationDelegate: navigationDelegate) .loadFileURL(url, allowingReadAccessTo: url)? @@ -1131,7 +1139,8 @@ protocol NewWindowPolicyDecisionMaker { }, navigationDidFail: { [weak self] _, _ in self?.restoreInteractionState(with: interactionStateData) }) - } else { + + default: restoreInteractionState(with: interactionStateData) } diff --git a/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift index 2da642e537..23ce6e328e 100644 --- a/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift @@ -182,7 +182,7 @@ extension DownloadsTabExtension: NavigationResponder { ?? navigationResponse.mainFrameNavigation?.navigationAction guard navigationResponse.httpResponse?.isSuccessful != false, // download non-http responses - !navigationResponse.canShowMIMEType || navigationResponse.shouldDownload + !responseCanShowMIMEType(navigationResponse) || navigationResponse.shouldDownload // if user pressed Opt+Enter in the Address bar to download from a URL || (navigationResponse.mainFrameNavigation?.redirectHistory.last ?? navigationResponse.mainFrameNavigation?.navigationAction)?.navigationType == .custom(.userRequestedPageDownload) else { @@ -199,6 +199,15 @@ extension DownloadsTabExtension: NavigationResponder { return .download } + private func responseCanShowMIMEType(_ response: NavigationResponse) -> Bool { + if response.canShowMIMEType { + return true + } else if response.url.isFileURL { + return Bundle.main.fileTypeExtensions.contains(response.url.pathExtension) + } + return false + } + @MainActor func navigationAction(_ navigationAction: NavigationAction, didBecome download: WebKitDownload) { enqueueDownload(download, withNavigationAction: navigationAction) From 44f0cce6035bf50a22fdb841fb86add94bd4dbf1 Mon Sep 17 00:00:00 2001 From: Alexey Martemyanov Date: Wed, 10 Apr 2024 15:44:22 +0500 Subject: [PATCH 27/40] Disable directory download (#2585) Task/Issue URL: https://app.asana.com/0/1199230911884351/1201043708349615/f --- .../xcshareddata/swiftpm/Package.resolved | 2 +- .../Common/Extensions/URLExtension.swift | 7 ++ .../TabExtensions/DownloadsTabExtension.swift | 1 + .../Downloads/DownloadsIntegrationTests.swift | 94 +++++++++++++++++++ IntegrationTests/Tab/TabContentTests.swift | 4 +- 5 files changed, 105 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 12fabf86b4..a575d944d1 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -138,7 +138,7 @@ { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax", + "location" : "https://github.com/apple/swift-syntax.git", "state" : { "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", "version" : "509.1.1" diff --git a/DuckDuckGo/Common/Extensions/URLExtension.swift b/DuckDuckGo/Common/Extensions/URLExtension.swift index b79720a9d5..3d95ae07f1 100644 --- a/DuckDuckGo/Common/Extensions/URLExtension.swift +++ b/DuckDuckGo/Common/Extensions/URLExtension.swift @@ -475,6 +475,13 @@ extension URL { } } + var isDirectory: Bool { + var isDirectory: ObjCBool = false + guard isFileURL, + FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) else { return false } + return isDirectory.boolValue + } + mutating func setFileHidden(_ hidden: Bool) throws { var resourceValues = URLResourceValues() resourceValues.isHidden = true diff --git a/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift index 23ce6e328e..1aae34f4c6 100644 --- a/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift +++ b/DuckDuckGo/Tab/TabExtensions/DownloadsTabExtension.swift @@ -182,6 +182,7 @@ extension DownloadsTabExtension: NavigationResponder { ?? navigationResponse.mainFrameNavigation?.navigationAction guard navigationResponse.httpResponse?.isSuccessful != false, // download non-http responses + !navigationResponse.url.isDirectory, // don‘t download a local directory !responseCanShowMIMEType(navigationResponse) || navigationResponse.shouldDownload // if user pressed Opt+Enter in the Address bar to download from a URL || (navigationResponse.mainFrameNavigation?.redirectHistory.last ?? navigationResponse.mainFrameNavigation?.navigationAction)?.navigationType == .custom(.userRequestedPageDownload) diff --git a/IntegrationTests/Downloads/DownloadsIntegrationTests.swift b/IntegrationTests/Downloads/DownloadsIntegrationTests.swift index d108921c11..fbe3f5f557 100644 --- a/IntegrationTests/Downloads/DownloadsIntegrationTests.swift +++ b/IntegrationTests/Downloads/DownloadsIntegrationTests.swift @@ -91,6 +91,100 @@ class DownloadsIntegrationTests: XCTestCase { XCTAssertEqual(try? Data(contentsOf: fileUrl), data.html) } + @MainActor + func testWhenUnsupportedMimeType_downloadStarts() async throws { + let preferences = DownloadsPreferences.shared + preferences.alwaysRequestDownloadLocation = false + preferences.selectedDownloadLocation = FileManager.default.temporaryDirectory + + let downloadTaskFuture = FileDownloadManager.shared.downloadsPublisher.timeout(5).first().promise() + let suffix = Int.random(in: 0.. Date: Wed, 10 Apr 2024 20:01:44 +0500 Subject: [PATCH 28/40] drop Downloads storyboard (#2556) Task/Issue URL: https://app.asana.com/0/1199230911884351/1206643583260394/f --- DuckDuckGo.xcodeproj/project.pbxproj | 70 ++- .../View/BookmarkOutlineCellView.swift | 2 + DuckDuckGo/Common/Localizables/UserText.swift | 2 +- .../View/AppKit/PreviewViewController.swift | 77 +++ .../Model}/DownloadListStoreMock.swift | 5 +- .../FileDownload/View/Downloads.storyboard | 483 ------------------ .../FileDownload/View/DownloadsCellView.swift | 231 ++++++++- .../FileDownload/View/DownloadsPopover.swift | 2 +- .../View/DownloadsViewController.swift | 266 ++++++---- .../View/NoDownloadsCellView.swift | 82 +++ .../View/OpenDownloadsCellView.swift | 67 +++ DuckDuckGo/Localizable.xcstrings | 60 --- 12 files changed, 672 insertions(+), 675 deletions(-) create mode 100644 DuckDuckGo/Common/View/AppKit/PreviewViewController.swift rename {UnitTests/FileDownload/Helpers => DuckDuckGo/FileDownload/Model}/DownloadListStoreMock.swift (92%) delete mode 100644 DuckDuckGo/FileDownload/View/Downloads.storyboard create mode 100644 DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift create mode 100644 DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 0ece6426d8..415aeb2e28 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -706,7 +706,6 @@ 3706FCB8293F65D500E42796 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85B7184927677C2D00B4277F /* Onboarding.storyboard */; }; 3706FCB9293F65D500E42796 /* FireproofDomains.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B0511AD262CAA5A00F6079C /* FireproofDomains.storyboard */; }; 3706FCBA293F65D500E42796 /* clickToLoadConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = EA47767F272A21B700419EDA /* clickToLoadConfig.json */; }; - 3706FCBB293F65D500E42796 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6B1E88126D5DAC30062C350 /* Downloads.storyboard */; }; 3706FCBC293F65D500E42796 /* dark-shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396F2754D4E900B241FA /* dark-shield.json */; }; 3706FCBD293F65D500E42796 /* dark-shield-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6EA27E880AE00036718 /* dark-shield-mouse-over.json */; }; 3706FCBE293F65D500E42796 /* autoconsent-bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */; }; @@ -805,7 +804,6 @@ 3706FE10293F661700E42796 /* TestNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6DA06E02913AEDB00225DE2 /* TestNavigationDelegate.swift */; }; 3706FE11293F661700E42796 /* URLSuggestedFilenameTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8553FF51257523760029327F /* URLSuggestedFilenameTests.swift */; }; 3706FE13293F661700E42796 /* ConfigurationStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85AC3B4825DAC9BD00C7D2AA /* ConfigurationStorageTests.swift */; }; - 3706FE14293F661700E42796 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; 3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA91F83827076F1900771A0D /* PrivacyIconViewModelTests.swift */; }; 3706FE16293F661700E42796 /* CSVImporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B723E0126B0003E00E14D75 /* CSVImporterTests.swift */; }; 3706FE19293F661700E42796 /* DeviceAuthenticatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BBC16A427C488C900E00A38 /* DeviceAuthenticatorTests.swift */; }; @@ -1971,7 +1969,6 @@ 4B957BF32AC7AE700062CA31 /* Onboarding.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 85B7184927677C2D00B4277F /* Onboarding.storyboard */; }; 4B957BF42AC7AE700062CA31 /* FireproofDomains.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B0511AD262CAA5A00F6079C /* FireproofDomains.storyboard */; }; 4B957BF52AC7AE700062CA31 /* clickToLoadConfig.json in Resources */ = {isa = PBXBuildFile; fileRef = EA47767F272A21B700419EDA /* clickToLoadConfig.json */; }; - 4B957BF62AC7AE700062CA31 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6B1E88126D5DAC30062C350 /* Downloads.storyboard */; }; 4B957BF72AC7AE700062CA31 /* dark-shield.json in Resources */ = {isa = PBXBuildFile; fileRef = AA34396F2754D4E900B241FA /* dark-shield.json */; }; 4B957BF82AC7AE700062CA31 /* BookmarksBarPromptAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 859F30662A72B38500C20372 /* BookmarksBarPromptAssets.xcassets */; }; 4B957BF92AC7AE700062CA31 /* dark-shield-mouse-over.json in Resources */ = {isa = PBXBuildFile; fileRef = AA7EB6EA27E880AE00036718 /* dark-shield-mouse-over.json */; }; @@ -2931,7 +2928,6 @@ B662D3DF275616FF0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B662D3DD275613BB0035D4D6 /* EncryptionKeyStoreMock.swift */; }; B6656E0D2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; B6656E0E2B29C733008798A1 /* FileImportViewLocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6656E0C2B29C733008798A1 /* FileImportViewLocalizationTests.swift */; }; - B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; B6656E5B2B2ADB1C008798A1 /* RequestFilePermissionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B5F5832B03580A008DB58A /* RequestFilePermissionView.swift */; }; B6676BE12AA986A700525A21 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; B6676BE22AA986A700525A21 /* AddressBarTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6676BE02AA986A700525A21 /* AddressBarTextEditor.swift */; }; @@ -3080,7 +3076,6 @@ B6B1E87B26D381710062C350 /* DownloadListCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */; }; B6B1E87E26D5DA0E0062C350 /* DownloadsPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */; }; B6B1E88026D5DA9B0062C350 /* DownloadsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */; }; - B6B1E88226D5DAC30062C350 /* Downloads.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6B1E88126D5DAC30062C350 /* Downloads.storyboard */; }; B6B1E88426D5EB570062C350 /* DownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */; }; B6B1E88B26D774090062C350 /* LinkButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B1E88A26D774090062C350 /* LinkButton.swift */; }; B6B2400E28083B49001B8F3A /* WebViewContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B2400D28083B49001B8F3A /* WebViewContainerView.swift */; }; @@ -3185,6 +3180,18 @@ B6E1491029A5C30500AAFBE8 /* ContentBlockingTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D574B12947224C008ED1B6 /* ContentBlockingTabExtension.swift */; }; B6E1491129A5C30A00AAFBE8 /* FBProtectionTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6D574B329472253008ED1B6 /* FBProtectionTabExtension.swift */; }; B6E319382953446000DD3BCF /* Assertions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E319372953446000DD3BCF /* Assertions.swift */; }; + B6E3E5502BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */; }; + B6E3E5512BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */; }; + B6E3E5522BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */; }; + B6E3E5542BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */; }; + B6E3E5552BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */; }; + B6E3E5562BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */; }; + B6E3E5582BBFD51400A41922 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */; }; + B6E3E5592BBFD51400A41922 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */; }; + B6E3E55A2BBFD51400A41922 /* PreviewViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */; }; + B6E3E55B2BC0041900A41922 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; + B6E3E55C2BC0041A00A41922 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; + B6E3E55D2BC0041C00A41922 /* DownloadListStoreMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */; }; B6E61EE3263AC0C8004E11AB /* FileManagerExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E61EE2263AC0C8004E11AB /* FileManagerExtension.swift */; }; B6E6B9E32BA1F5F1008AA7E1 /* FilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */; }; B6E6B9E42BA1F5F1008AA7E1 /* FilePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */; }; @@ -4663,7 +4670,6 @@ B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadListCoordinator.swift; sourceTree = ""; }; B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsPopover.swift; sourceTree = ""; }; B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsViewController.swift; sourceTree = ""; }; - B6B1E88126D5DAC30062C350 /* Downloads.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Downloads.storyboard; sourceTree = ""; }; B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadsCellView.swift; sourceTree = ""; }; B6B1E88A26D774090062C350 /* LinkButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkButton.swift; sourceTree = ""; }; B6B2400D28083B49001B8F3A /* WebViewContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewContainerView.swift; sourceTree = ""; }; @@ -4727,6 +4733,9 @@ B6DB3CFA26A17CB800D459B7 /* PermissionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionModel.swift; sourceTree = ""; }; B6DE57F52B05EA9000CD54B9 /* SheetHostingWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SheetHostingWindow.swift; sourceTree = ""; }; B6E319372953446000DD3BCF /* Assertions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assertions.swift; sourceTree = ""; }; + B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenDownloadsCellView.swift; sourceTree = ""; }; + B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoDownloadsCellView.swift; sourceTree = ""; }; + B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewViewController.swift; sourceTree = ""; }; B6E61EE2263AC0C8004E11AB /* FileManagerExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManagerExtension.swift; sourceTree = ""; }; B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilePresenter.swift; sourceTree = ""; }; B6E6B9E82BA1FA1C008AA7E1 /* SandboxTestTool.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SandboxTestTool.xcconfig; sourceTree = ""; }; @@ -6019,8 +6028,8 @@ 4B67743D255DBEEA00025BD8 /* Database */ = { isa = PBXGroup; children = ( - 4B677440255DBEEA00025BD8 /* Database.swift */, B6085D052743905F00A9C456 /* CoreDataStore.swift */, + 4B677440255DBEEA00025BD8 /* Database.swift */, ); path = Database; sourceTree = ""; @@ -6694,6 +6703,7 @@ isa = PBXGroup; children = ( B6C0B23526E732000031CB7F /* DownloadListItem.swift */, + B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */, B6C0B23D26E8BF1F0031CB7F /* DownloadListViewModel.swift */, B6CC26672BAD959500F53F8D /* DownloadProgress.swift */, B6104E9A2BA9C173008636B2 /* DownloadResumeData.swift */, @@ -6701,10 +6711,10 @@ B6C0B23826E742610031CB7F /* FileDownloadError.swift */, 856C98DE257014BD00A22F1F /* FileDownloadManager.swift */, B6E6B9E22BA1F5F1008AA7E1 /* FilePresenter.swift */, - B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */, + B6CC266B2BAD9CD800F53F8D /* FileProgressPresenter.swift */, B6ABD0CD2BC042CE0000EB69 /* NSURL+sandboxExtensionRetainCount.m */, + B6ABD0C92BC03F610000EB69 /* SecurityScopedFileURLController.swift */, B6A924D82664C72D001A28CA /* WebKitDownloadTask.swift */, - B6CC266B2BAD9CD800F53F8D /* FileProgressPresenter.swift */, ); path = Model; sourceTree = ""; @@ -6768,26 +6778,27 @@ 8585B63626D6E61500C1416F /* AppKit */ = { isa = PBXGroup; children = ( + 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */, B65E6B9D26D9EC0800095F96 /* CircularProgressView.swift */, B693954626F04BEA0015B914 /* ColorView.swift */, + 4B379C2327BDE1B0008A968E /* FlatButton.swift */, B693953E26F04BE70015B914 /* FocusRingView.swift */, B693954326F04BE90015B914 /* GradientView.swift */, - B6B1E88A26D774090062C350 /* LinkButton.swift */, B6B140872ABDBCC1004F8E85 /* HoverTrackingArea.swift */, + B6B1E88A26D774090062C350 /* LinkButton.swift */, + B693954026F04BE80015B914 /* LoadingProgressView.swift */, B693954426F04BE90015B914 /* LongPressButton.swift */, - B693954926F04BEB0015B914 /* MouseOverButton.swift */, AA7EB6DE27E7C57D00036718 /* MouseOverAnimationButton.swift */, + B693954926F04BEB0015B914 /* MouseOverButton.swift */, B693953D26F04BE70015B914 /* MouseOverView.swift */, - 4B379C2327BDE1B0008A968E /* FlatButton.swift */, + 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */, B693954726F04BEA0015B914 /* NSSavePanelExtension.swift */, B693954126F04BE80015B914 /* PaddedImageButton.swift */, - B693954026F04BE80015B914 /* LoadingProgressView.swift */, + B6E3E5572BBFD51400A41922 /* PreviewViewController.swift */, B60C6F8C29B200AB007BFAA8 /* SavePanelAccessoryView.swift */, B693954226F04BE90015B914 /* ShadowView.swift */, - B693954526F04BEA0015B914 /* WindowDraggingView.swift */, 4BDFA4AD27BF19E500648192 /* ToggleableScrollView.swift */, - 85774B022A71CDD000DE0561 /* BlockMenuItem.swift */, - 1D26EBAB2B74BECB0002A93F /* NSImageSendable.swift */, + B693954526F04BEA0015B914 /* WindowDraggingView.swift */, ); path = AppKit; sourceTree = ""; @@ -8589,11 +8600,12 @@ B6B1E87C26D5DA020062C350 /* View */ = { isa = PBXGroup; children = ( + B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */, B6B1E87D26D5DA0E0062C350 /* DownloadsPopover.swift */, B6B1E87F26D5DA9B0062C350 /* DownloadsViewController.swift */, - B6B1E88126D5DAC30062C350 /* Downloads.storyboard */, - B6B1E88326D5EB570062C350 /* DownloadsCellView.swift */, + B6E3E5532BBFCEE300A41922 /* NoDownloadsCellView.swift */, B6C0B23B26E87D900031CB7F /* NSAlert+ActiveDownloadsTermination.swift */, + B6E3E54F2BBFCDEE00A41922 /* OpenDownloadsCellView.swift */, ); path = View; sourceTree = ""; @@ -8638,9 +8650,9 @@ B6C0B23126E71A800031CB7F /* Services */ = { isa = PBXGroup; children = ( - B6C0B23226E71BCD0031CB7F /* Downloads.xcdatamodeld */, - B6C0B22F26E61D630031CB7F /* DownloadListStore.swift */, B6B1E87A26D381710062C350 /* DownloadListCoordinator.swift */, + B6C0B22F26E61D630031CB7F /* DownloadListStore.swift */, + B6C0B23226E71BCD0031CB7F /* Downloads.xcdatamodeld */, ); path = Services; sourceTree = ""; @@ -8698,7 +8710,6 @@ B693956726F352DB0015B914 /* DownloadsWebViewMock.h */, B693956826F352DB0015B914 /* DownloadsWebViewMock.m */, B630794126731F5400DCEE41 /* WKDownloadMock.swift */, - B693956026F1C1BC0015B914 /* DownloadListStoreMock.swift */, B693956226F1C2A40015B914 /* FileDownloadManagerMock.swift */, 9F180D112B69C665000D695F /* DownloadsTabExtensionMock.swift */, ); @@ -9596,7 +9607,6 @@ 56CEE90F2B7A725C00CF10AA /* InfoPlist.xcstrings in Resources */, 3706FCB9293F65D500E42796 /* FireproofDomains.storyboard in Resources */, 3706FCBA293F65D500E42796 /* clickToLoadConfig.json in Resources */, - 3706FCBB293F65D500E42796 /* Downloads.storyboard in Resources */, 3706FCBC293F65D500E42796 /* dark-shield.json in Resources */, 854DAAAE2A72B613001E2E24 /* BookmarksBarPromptAssets.xcassets in Resources */, 3706FCBD293F65D500E42796 /* dark-shield-mouse-over.json in Resources */, @@ -9725,7 +9735,6 @@ 56CEE9102B7A72FE00CF10AA /* InfoPlist.xcstrings in Resources */, 4B957BF42AC7AE700062CA31 /* FireproofDomains.storyboard in Resources */, 4B957BF52AC7AE700062CA31 /* clickToLoadConfig.json in Resources */, - 4B957BF62AC7AE700062CA31 /* Downloads.storyboard in Resources */, 4B957BF72AC7AE700062CA31 /* dark-shield.json in Resources */, 4B957BF82AC7AE700062CA31 /* BookmarksBarPromptAssets.xcassets in Resources */, 4B957BF92AC7AE700062CA31 /* dark-shield-mouse-over.json in Resources */, @@ -9841,7 +9850,6 @@ 56CEE90E2B7A725B00CF10AA /* InfoPlist.xcstrings in Resources */, 4B0511C3262CAA5A00F6079C /* FireproofDomains.storyboard in Resources */, EA477680272A21B700419EDA /* clickToLoadConfig.json in Resources */, - B6B1E88226D5DAC30062C350 /* Downloads.storyboard in Resources */, AA3439712754D4E900B241FA /* dark-shield.json in Resources */, 859F30672A72B38500C20372 /* BookmarksBarPromptAssets.xcassets in Resources */, AA7EB6EB27E880AE00036718 /* dark-shield-mouse-over.json in Resources */, @@ -10340,6 +10348,7 @@ 3706FAAD293F65D500E42796 /* BadgeNotificationAnimationModel.swift in Sources */, 3706FAAE293F65D500E42796 /* HyperLink.swift in Sources */, 3706FAAF293F65D500E42796 /* PasteboardWriting.swift in Sources */, + B6E3E5512BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */, 3706FAB0293F65D500E42796 /* BookmarkOutlineCellView.swift in Sources */, 3706FAB1293F65D500E42796 /* UnprotectedDomains.xcdatamodeld in Sources */, 85393C872A6FF1B600F11EB3 /* BookmarksBarAppearance.swift in Sources */, @@ -10479,6 +10488,7 @@ 3706FB17293F65D500E42796 /* FirePopoverCollectionViewHeader.swift in Sources */, 85774B042A71CDD000DE0561 /* BlockMenuItem.swift in Sources */, 3706FB19293F65D500E42796 /* FireViewController.swift in Sources */, + B6E3E55C2BC0041A00A41922 /* DownloadListStoreMock.swift in Sources */, 4B4D60D42A0C84F700BCD287 /* UserText+NetworkProtection.swift in Sources */, 3707C71F294B5D2900682A9F /* WKUserContentControllerExtension.swift in Sources */, 3706FB1A293F65D500E42796 /* OutlineSeparatorViewCell.swift in Sources */, @@ -10843,6 +10853,7 @@ 3706FC13293F65D500E42796 /* FaviconView.swift in Sources */, B69A14F72B4D701F00B9417D /* AddBookmarkPopoverViewModel.swift in Sources */, 3706FC14293F65D500E42796 /* OnboardingFlow.swift in Sources */, + B6E3E5592BBFD51400A41922 /* PreviewViewController.swift in Sources */, EEC8EB3E2982CA3B0065AA39 /* JSAlertViewModel.swift in Sources */, 3706FC16293F65D500E42796 /* PasswordManagementLoginModel.swift in Sources */, 3706FC17293F65D500E42796 /* TabViewModel.swift in Sources */, @@ -10923,6 +10934,7 @@ 3706FC4E293F65D500E42796 /* AtbAndVariantCleanup.swift in Sources */, 3706FC50293F65D500E42796 /* FeedbackWindow.swift in Sources */, 3706FC51293F65D500E42796 /* RecentlyVisitedView.swift in Sources */, + B6E3E5552BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */, B645D8F729FA95440024461F /* WKProcessPoolExtension.swift in Sources */, 9F514F922B7D88AD001832A9 /* AddEditBookmarkFolderDialogView.swift in Sources */, 3706FC52293F65D500E42796 /* MouseOverAnimationButton.swift in Sources */, @@ -11120,7 +11132,6 @@ 4BE344EF2B23786F003FC223 /* VPNFeedbackFormViewModelTests.swift in Sources */, B6F56569299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, 3706FE13293F661700E42796 /* ConfigurationStorageTests.swift in Sources */, - 3706FE14293F661700E42796 /* DownloadListStoreMock.swift in Sources */, 3706FE15293F661700E42796 /* PrivacyIconViewModelTests.swift in Sources */, B68412212B6A30680092F66A /* StringExtensionTests.swift in Sources */, 1D8C2FEE2B70F5D0005E4BBD /* MockViewSnapshotRenderer.swift in Sources */, @@ -11628,6 +11639,7 @@ 1D01A3D22B88CEC600FE8150 /* PreferencesAccessibilityView.swift in Sources */, 4B9579A02AC7AE700062CA31 /* InvitedToWaitlistView.swift in Sources */, 4B9579A22AC7AE700062CA31 /* SaveCredentialsViewController.swift in Sources */, + B6E3E55D2BC0041C00A41922 /* DownloadListStoreMock.swift in Sources */, 4B9579A32AC7AE700062CA31 /* PopUpButton.swift in Sources */, 4B9579A42AC7AE700062CA31 /* NetworkProtectionInviteDialog.swift in Sources */, 4B9579A52AC7AE700062CA31 /* SuggestionViewController.swift in Sources */, @@ -11660,6 +11672,7 @@ 4B9579C02AC7AE700062CA31 /* DebugUserScript.swift in Sources */, 1DC669722B6CF0D700AA0645 /* TabSnapshotStore.swift in Sources */, 4B9579C12AC7AE700062CA31 /* RecentlyClosedTab.swift in Sources */, + B6E3E55A2BBFD51400A41922 /* PreviewViewController.swift in Sources */, 4B9579C22AC7AE700062CA31 /* PDFSearchTextMenuItemHandler.swift in Sources */, 4B9579C42AC7AE700062CA31 /* HistoryMenu.swift in Sources */, 4B9579C52AC7AE700062CA31 /* ContentScopeFeatureFlagging.swift in Sources */, @@ -11860,6 +11873,7 @@ 4B957A6A2AC7AE700062CA31 /* SecureVaultLoginImporter.swift in Sources */, 4B957A6B2AC7AE700062CA31 /* WKProcessPoolExtension.swift in Sources */, 4B957A6D2AC7AE700062CA31 /* LoginItemsManager.swift in Sources */, + B6E3E5562BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */, 4B957A6E2AC7AE700062CA31 /* PixelExperiment.swift in Sources */, 4B957A6F2AC7AE700062CA31 /* DuckPlayerTabExtension.swift in Sources */, 4B957A702AC7AE700062CA31 /* RecentlyClosedCoordinator.swift in Sources */, @@ -12100,6 +12114,7 @@ 7BEC20472B0F505F00243D3E /* AddBookmarkFolderPopoverView.swift in Sources */, 4B957B392AC7AE700062CA31 /* StatisticsStore.swift in Sources */, EEC4A66B2B2C87D300F7C0AA /* VPNLocationView.swift in Sources */, + B6E3E5522BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */, 4B957B3A2AC7AE700062CA31 /* BWInstallationService.swift in Sources */, 4B957B3B2AC7AE700062CA31 /* BookmarksBarPromptPopover.swift in Sources */, 4B957B3C2AC7AE700062CA31 /* NetworkProtectionInvitePresenter.swift in Sources */, @@ -12372,6 +12387,7 @@ 0230C0A3272080090018F728 /* KeyedCodingExtension.swift in Sources */, 31AA6B972B960B870025014E /* DataBrokerProtectionLoginItemPixels.swift in Sources */, B6BF5D852946FFDA006742B1 /* PrivacyDashboardTabExtension.swift in Sources */, + B6E3E55B2BC0041900A41922 /* DownloadListStoreMock.swift in Sources */, B6C0B23026E61D630031CB7F /* DownloadListStore.swift in Sources */, 85799C1825DEBB3F0007EC87 /* Logging.swift in Sources */, AAC30A2E268F1EE300D2D9CD /* CrashReportPromptPresenter.swift in Sources */, @@ -12457,6 +12473,7 @@ AA3D531727A1EEED00074EC1 /* FeedbackViewController.swift in Sources */, AAEF6BC8276A081C0024DCF4 /* FaviconSelector.swift in Sources */, 4B2E7D6326FF9D6500D2DB17 /* PrintingUserScript.swift in Sources */, + B6E3E5542BBFCEE300A41922 /* NoDownloadsCellView.swift in Sources */, 4BBDEE9428FC14760092FAA6 /* ConnectBitwardenViewController.swift in Sources */, 1DDF076428F815AD00EDFBE3 /* BWManager.swift in Sources */, 9833912F27AAA3CE00DAF119 /* AppTrackerDataSetProvider.swift in Sources */, @@ -12944,6 +12961,7 @@ B6BBF17427475B15004F850E /* PopupBlockedPopover.swift in Sources */, 8589063A267BCD8E00D23B0D /* SaveCredentialsPopover.swift in Sources */, 987799F32999993C005D8EB6 /* LegacyBookmarksStoreMigration.swift in Sources */, + B6E3E5582BBFD51400A41922 /* PreviewViewController.swift in Sources */, 4B379C1527BD91E3008A968E /* QuartzIdleStateProvider.swift in Sources */, 37F19A6728E1B43200740DC6 /* DuckPlayerPreferences.swift in Sources */, 1D01A3D42B88CF7700FE8150 /* AccessibilityPreferences.swift in Sources */, @@ -13075,6 +13093,7 @@ 1DDD3EC02B84F5D5004CBF2B /* PreferencesCookiePopupProtectionView.swift in Sources */, B693955026F04BEB0015B914 /* ShadowView.swift in Sources */, AA3D531D27A2F58F00074EC1 /* FeedbackSender.swift in Sources */, + B6E3E5502BBFCDEE00A41922 /* OpenDownloadsCellView.swift in Sources */, B6BDDA012942389000F68088 /* TabExtensions.swift in Sources */, AA7412B224D0B3AC00D22FE0 /* TabBarViewItem.swift in Sources */, 856C98D52570116900A22F1F /* NSWindow+Toast.swift in Sources */, @@ -13159,7 +13178,6 @@ 1D3B1ABF29369FC8006F4388 /* BWEncryptionTests.swift in Sources */, B6F56567299A414300A04298 /* WKWebViewMockingExtension.swift in Sources */, 1D9FDEC62B9B64DB0040B78C /* PrivacyProtectionStatusTests.swift in Sources */, - B6656E122B29E3BE008798A1 /* DownloadListStoreMock.swift in Sources */, 37D23780287EFEE200BCE03B /* PinnedTabsManagerTests.swift in Sources */, AA0877BA26D5161D00B05660 /* WebKitVersionProviderTests.swift in Sources */, 9FA75A3E2BA00E1400DA5FA6 /* BookmarksBarMenuFactoryTests.swift in Sources */, diff --git a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift index 06b868c826..813e1ff8ec 100644 --- a/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift +++ b/DuckDuckGo/Bookmarks/View/BookmarkOutlineCellView.swift @@ -40,6 +40,8 @@ final class BookmarkOutlineCellView: NSTableCellView { init(identifier: NSUserInterfaceItemIdentifier) { super.init(frame: .zero) + self.identifier = identifier + setupUI() } diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index 00a327d353..36e9c3d90d 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -1061,7 +1061,7 @@ struct UserText { static let downloadsOpenWebsiteItem = NSLocalizedString("downloads.open-website.item", value: "Open Originating Website", comment: "Contextual menu item in downloads manager to open the downloaded file originating website") static let downloadsRemoveFromListItem = NSLocalizedString("downloads.remove-from-list.item", value: "Remove from List", comment: "Contextual menu item in downloads manager to remove the given downloaded from the list of downloaded files") static let downloadsStopItem = NSLocalizedString("downloads.stop.item", value: "Stop", comment: "Contextual menu item in downloads manager to stop the download") - static let downloadsRestartItem = NSLocalizedString("downloads.restart.item", value: "Stop", comment: "Contextual menu item in downloads manager to restart the download") + static let downloadsRestartItem = restartDownloadToolTip static let downloadsClearAllItem = NSLocalizedString("downloads.clear-all.item", value: "Clear All", comment: "Contextual menu item in downloads manager to clear all downloaded items from the list") static let downloadsNoRecentDownload = NSLocalizedString("downloads.no-recent-downloads", value: "No recent downloads", comment: "Label in the downloads manager that shows that there are no recently downloaded items") static let downloadsOpenDownloadsFolder = NSLocalizedString("downloads.open-downloads-folder", value: "Open Downloads Folder", comment: "Button in the downloads manager that allows the user to open the downloads folder") diff --git a/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift new file mode 100644 index 0000000000..d62feef70b --- /dev/null +++ b/DuckDuckGo/Common/View/AppKit/PreviewViewController.swift @@ -0,0 +1,77 @@ +// +// PreviewViewController.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +@resultBuilder +struct NSViewBuilder { + static func buildBlock(_ component: NSView) -> NSView { + return component + } +} + +#if DEBUG +/// Used to preview an NSView using Xcode #Preview macro +/// Usage: +/// ``` +/// @available(macOS 14.0, *) +/// #Preview { +/// PreviewViewController(showWindowTitle: false /*hide preview window title*/, adjustWindowFrame: true /*set the window size to the view size*/) { +/// MyNSView() +/// } +/// } +/// ``` +@available(macOS 14.0, *) +final class PreviewViewController: NSViewController { + let showWindowTitle: Bool + let adjustWindowFrame: Bool + + init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) { + self.showWindowTitle = showWindowTitle + self.adjustWindowFrame = adjustWindowFrame + super.init(nibName: nil, bundle: nil) + self.view = builder() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + override func viewDidAppear() { + guard let window = view.window else { return } + if !showWindowTitle { + window.titlebarAppearsTransparent = true + window.titleVisibility = .hidden + window.styleMask = [] + } + if adjustWindowFrame { + window.setFrame(NSRect(origin: .zero, size: view.bounds.size), display: true) + } + } + +} +#else +final class PreviewViewController: NSViewController { + init(showWindowTitle: Bool = true, adjustWindowFrame: Bool = false, @NSViewBuilder builder: () -> NSView) { + fatalError("only for DEBUG") + } + required init?(coder: NSCoder) { + fatalError("only for DEBUG") + } +} +#endif diff --git a/UnitTests/FileDownload/Helpers/DownloadListStoreMock.swift b/DuckDuckGo/FileDownload/Model/DownloadListStoreMock.swift similarity index 92% rename from UnitTests/FileDownload/Helpers/DownloadListStoreMock.swift rename to DuckDuckGo/FileDownload/Model/DownloadListStoreMock.swift index 28001ba4e1..edae68b750 100644 --- a/UnitTests/FileDownload/Helpers/DownloadListStoreMock.swift +++ b/DuckDuckGo/FileDownload/Model/DownloadListStoreMock.swift @@ -17,12 +17,12 @@ // import Foundation -@testable import DuckDuckGo_Privacy_Browser +#if DEBUG final class DownloadListStoreMock: DownloadListStoring { var fetchBlock: ((@escaping @MainActor (Result<[DownloadListItem], Error>) -> Void) -> Void)? - func fetch(completionHandler: @escaping @MainActor (Result<[DuckDuckGo_Privacy_Browser.DownloadListItem], any Error>) -> Void) { + func fetch(completionHandler: @escaping @MainActor (Result<[DownloadListItem], any Error>) -> Void) { fetchBlock?(completionHandler) } @@ -42,3 +42,4 @@ final class DownloadListStoreMock: DownloadListStoring { } } +#endif diff --git a/DuckDuckGo/FileDownload/View/Downloads.storyboard b/DuckDuckGo/FileDownload/View/Downloads.storyboard deleted file mode 100644 index 41e5511da3..0000000000 --- a/DuckDuckGo/FileDownload/View/Downloads.storyboard +++ /dev/null @@ -1,483 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/DuckDuckGo/FileDownload/View/DownloadsCellView.swift b/DuckDuckGo/FileDownload/View/DownloadsCellView.swift index ad3ed7bdd5..2c3f803924 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsCellView.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsCellView.swift @@ -22,6 +22,11 @@ import UniformTypeIdentifiers final class DownloadsCellView: NSTableCellView { + fileprivate enum Constants { + static let width: CGFloat = 420 + static let height: CGFloat = 60 + } + enum DownloadError: Error { case urlNotSet case fileRemoved @@ -38,13 +43,22 @@ final class DownloadsCellView: NSTableCellView { } } - @IBOutlet var titleLabel: NSTextField! - @IBOutlet var detailLabel: NSTextField! - @IBOutlet var progressView: CircularProgressView! - @IBOutlet var cancelButton: MouseOverButton! - @IBOutlet var revealButton: MouseOverButton! - @IBOutlet var restartButton: MouseOverButton! - @IBOutlet var separator: NSBox! + private let fileIconView = NSImageView() + private let titleLabel = NSTextField() + private let detailLabel = NSTextField() + + private let progressView = CircularProgressView() + private let cancelButton = MouseOverButton(image: .cancelDownload, + target: nil, + action: #selector(DownloadsViewController.cancelDownloadAction)) + private let revealButton = MouseOverButton(image: .revealDownload, + target: nil, + action: #selector(DownloadsViewController.revealDownloadAction)) + private let restartButton = MouseOverButton(image: .restartDownload, + target: nil, + action: #selector(DownloadsViewController.restartDownloadAction)) + + private let separator = NSBox() private var buttonOverCancellables = Set() private var cancellables = Set() @@ -82,7 +96,149 @@ final class DownloadsCellView: NSTableCellView { } } - override func awakeFromNib() { + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: CGRect(x: 0, y: 0, width: Constants.width, height: Constants.height)) + self.identifier = identifier + + setupUI() + subscribeToMouseOverEvents() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + // swiftlint:disable:next function_body_length + private func setupUI() { + self.imageView = fileIconView + self.wantsLayer = true + + addSubview(fileIconView) + addSubview(titleLabel) + addSubview(detailLabel) + addSubview(cancelButton) + addSubview(revealButton) + addSubview(restartButton) + addSubview(progressView) + addSubview(separator) + + fileIconView.translatesAutoresizingMaskIntoConstraints = false + fileIconView.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + fileIconView.setContentHuggingPriority(.init(rawValue: 251), for: .vertical) + fileIconView.imageScaling = .scaleProportionallyDown + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.isSelectable = false + titleLabel.drawsBackground = false + titleLabel.font = .systemFont(ofSize: 13) + titleLabel.textColor = .controlTextColor + titleLabel.lineBreakMode = .byTruncatingMiddle + titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + titleLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + + detailLabel.translatesAutoresizingMaskIntoConstraints = false + detailLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + detailLabel.isEditable = false + detailLabel.isBordered = false + detailLabel.isSelectable = false + detailLabel.drawsBackground = false + detailLabel.font = .systemFont(ofSize: 13) + detailLabel.textColor = .secondaryLabelColor + detailLabel.lineBreakMode = .byClipping + detailLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + detailLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + + progressView.translatesAutoresizingMaskIntoConstraints = false + + cancelButton.translatesAutoresizingMaskIntoConstraints = false + cancelButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + cancelButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + cancelButton.bezelStyle = .shadowlessSquare + cancelButton.isBordered = false + cancelButton.imagePosition = .imageOnly + cancelButton.imageScaling = .scaleProportionallyDown + cancelButton.cornerRadius = 4 + cancelButton.backgroundInset = CGPoint(x: 2, y: 2) + cancelButton.mouseDownColor = .buttonMouseDown + cancelButton.mouseOverColor = .buttonMouseOver + + revealButton.translatesAutoresizingMaskIntoConstraints = false + revealButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + revealButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + revealButton.alignment = .center + revealButton.bezelStyle = .shadowlessSquare + revealButton.isBordered = false + revealButton.imagePosition = .imageOnly + revealButton.imageScaling = .scaleProportionallyDown + revealButton.cornerRadius = 4 + revealButton.backgroundInset = CGPoint(x: 2, y: 2) + revealButton.mouseDownColor = .buttonMouseDown + revealButton.mouseOverColor = .buttonMouseOver + + restartButton.translatesAutoresizingMaskIntoConstraints = false + restartButton.setContentHuggingPriority(.defaultHigh, for: .horizontal) + restartButton.setContentHuggingPriority(.defaultHigh, for: .vertical) + restartButton.alignment = .center + restartButton.bezelStyle = .shadowlessSquare + restartButton.isBordered = false + restartButton.imagePosition = .imageOnly + restartButton.imageScaling = .scaleProportionallyDown + restartButton.cornerRadius = 4 + restartButton.backgroundInset = CGPoint(x: 2, y: 2) + restartButton.mouseDownColor = .buttonMouseDown + restartButton.mouseOverColor = .buttonMouseOver + + separator.boxType = .separator + separator.translatesAutoresizingMaskIntoConstraints = false + + setupLayout() + } + + private func setupLayout() { + NSLayoutConstraint.activate([ + fileIconView.heightAnchor.constraint(equalToConstant: 32), + fileIconView.widthAnchor.constraint(equalToConstant: 32), + fileIconView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 7), + fileIconView.centerYAnchor.constraint(equalTo: centerYAnchor), + + titleLabel.heightAnchor.constraint(equalToConstant: 16), + titleLabel.leadingAnchor.constraint(equalTo: fileIconView.trailingAnchor, constant: 6), + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12), + detailLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), + + cancelButton.heightAnchor.constraint(equalToConstant: 32), + cancelButton.widthAnchor.constraint(equalToConstant: 32), + cancelButton.leadingAnchor.constraint(equalTo: titleLabel.trailingAnchor, constant: 8), + cancelButton.leadingAnchor.constraint(equalTo: detailLabel.trailingAnchor, constant: 8), + cancelButton.centerYAnchor.constraint(equalTo: centerYAnchor), + trailingAnchor.constraint(equalTo: cancelButton.trailingAnchor, constant: 4), + + revealButton.widthAnchor.constraint(equalToConstant: 32), + revealButton.heightAnchor.constraint(equalToConstant: 32), + revealButton.centerYAnchor.constraint(equalTo: cancelButton.centerYAnchor), + revealButton.centerXAnchor.constraint(equalTo: cancelButton.centerXAnchor), + + restartButton.widthAnchor.constraint(equalToConstant: 32), + restartButton.heightAnchor.constraint(equalToConstant: 32), + restartButton.centerXAnchor.constraint(equalTo: revealButton.centerXAnchor), + restartButton.centerYAnchor.constraint(equalTo: revealButton.centerYAnchor), + + progressView.widthAnchor.constraint(equalToConstant: 27), + progressView.heightAnchor.constraint(equalToConstant: 27), + progressView.centerXAnchor.constraint(equalTo: cancelButton.centerXAnchor), + progressView.centerYAnchor.constraint(equalTo: cancelButton.centerYAnchor), + + separator.topAnchor.constraint(equalTo: detailLabel.bottomAnchor, constant: 12), + separator.leadingAnchor.constraint(equalTo: leadingAnchor), + trailingAnchor.constraint(equalTo: separator.trailingAnchor), + bottomAnchor.constraint(equalTo: separator.bottomAnchor), + ]) + } + + private func subscribeToMouseOverEvents() { cancelButton.$isMouseOver.sink { [weak self] isMouseOver in self?.onButtonMouseOverChange?(isMouseOver) }.store(in: &buttonOverCancellables) @@ -163,8 +319,10 @@ final class DownloadsCellView: NSTableCellView { .store(in: &cancellables) } - private static let fileRemovedTitleAttributes: [NSAttributedString.Key: Any] = [.strikethroughStyle: 1, - .foregroundColor: NSColor.disabledControlTextColor] + private static let fileRemovedTitleAttributes: [NSAttributedString.Key: Any] = [ + .strikethroughStyle: 1, + .foregroundColor: NSColor.disabledControlTextColor + ] private func updateFilename(_ filename: String, state: DownloadViewModel.State) { // hide progress with animation on completion/failure @@ -357,3 +515,56 @@ extension DownloadsCellView.DownloadError: LocalizedError { } } + +#if DEBUG +@available(macOS 14.0, *) +#Preview { + DownloadsCellView.PreviewView() +} +@available(macOS 14.0, *) +let previewDownloadListItems = [ + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Indefinite progress download with long filename for clipping.zip", progress: Progress(totalUnitCount: -1), isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Active download.pdf", progress: Progress(totalUnitCount: 100, completedUnitCount: 42), isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Completed download.dmg", progress: nil, isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: nil, tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Non-retryable download.txt", progress: nil, isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: nil), + DownloadListItem(identifier: .init(), added: .now, modified: .now, downloadURL: .empty, websiteURL: nil, fileName: "Retryable download.rtf", progress: nil, isBurner: false, destinationURL: URL(fileURLWithPath: "\(#file)"), destinationFileBookmarkData: nil, tempURL: URL(fileURLWithPath: "\(#file)"), tempFileBookmarkData: nil, error: FileDownloadError(URLError(.networkConnectionLost, userInfo: ["isRetryable": true]) as NSError)), +] +@available(macOS 14.0, *) +extension DownloadsCellView { + final class PreviewView: NSView { + + init() { + super.init(frame: .zero) + translatesAutoresizingMaskIntoConstraints = true + + let cells = [ + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + DownloadsCellView(identifier: .init("")), + ] + + for (idx, cell) in cells.enumerated() { + cell.widthAnchor.constraint(equalToConstant: 420).isActive = true + cell.heightAnchor.constraint(equalToConstant: 60).isActive = true + let item = previewDownloadListItems[idx] + cell.objectValue = DownloadViewModel(item: item) + } + + let stackView = NSStackView(views: cells as [NSView]) + stackView.orientation = .vertical + stackView.spacing = 1 + addAndLayout(stackView) + + widthAnchor.constraint(equalToConstant: 420).isActive = true + heightAnchor.constraint(equalToConstant: CGFloat((60 + 1) * cells.count)).isActive = true + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + } +} +#endif diff --git a/DuckDuckGo/FileDownload/View/DownloadsPopover.swift b/DuckDuckGo/FileDownload/View/DownloadsPopover.swift index 31bc627e2f..b41bf222c3 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsPopover.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsPopover.swift @@ -38,7 +38,7 @@ final class DownloadsPopover: NSPopover { // swiftlint:enable force_cast private func setupContentController() { - let controller = DownloadsViewController.create() + let controller = DownloadsViewController() contentViewController = controller } diff --git a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift index dac2e0bec7..ddc8c1aeb5 100644 --- a/DuckDuckGo/FileDownload/View/DownloadsViewController.swift +++ b/DuckDuckGo/FileDownload/View/DownloadsViewController.swift @@ -20,58 +20,157 @@ import Cocoa import Combine protocol DownloadsViewControllerDelegate: AnyObject { - func clearDownloadsActionTriggered() - } final class DownloadsViewController: NSViewController { static let preferredContentSize = CGSize(width: 420, height: 500) - static func create() -> Self { - let storyboard = NSStoryboard(name: "Downloads", bundle: nil) - // swiftlint:disable force_cast - let controller = storyboard.instantiateInitialController() as! Self - controller.loadView() - // swiftlint:enable force_cast - return controller - } - - @IBOutlet weak var openItem: NSMenuItem! - @IBOutlet weak var showInFinderItem: NSMenuItem! - @IBOutlet weak var copyDownloadLinkItem: NSMenuItem! - @IBOutlet weak var openWebsiteItem: NSMenuItem! - @IBOutlet weak var removeFromListItem: NSMenuItem! - @IBOutlet weak var stopItem: NSMenuItem! - @IBOutlet weak var restartItem: NSMenuItem! - @IBOutlet weak var clearAllItem: NSMenuItem! - - @IBOutlet weak var titleLabel: NSTextField! + private lazy var titleLabel = NSTextField(string: UserText.downloadsDialogTitle) - @IBOutlet var openDownloadsFolderButton: NSButton! - @IBOutlet var clearDownloadsButton: NSButton! + private lazy var openDownloadsFolderButton = MouseOverButton(image: .openDownloadsFolder, target: self, action: #selector(openDownloadsFolderAction)) + private lazy var clearDownloadsButton = MouseOverButton(image: .clearDownloads, target: self, action: #selector(clearDownloadsAction)) - @IBOutlet var contextMenu: NSMenu! - @IBOutlet var tableView: NSTableView! - @IBOutlet var tableViewHeightConstraint: NSLayoutConstraint? + private lazy var scrollView = NSScrollView() + private lazy var tableView = NSTableView() + private var tableViewHeightConstraint: NSLayoutConstraint! private var cellIndexToUnselect: Int? weak var delegate: DownloadsViewControllerDelegate? - var viewModel = DownloadListViewModel() - var downloadsCancellable: AnyCancellable? + private let viewModel: DownloadListViewModel + private var downloadsCancellable: AnyCancellable? - override func viewDidLoad() { - super.viewDidLoad() + init(viewModel: DownloadListViewModel? = nil) { + self.viewModel = viewModel ?? DownloadListViewModel() + super.init(nibName: nil, bundle: nil) + } - setupDragAndDrop() - setUpStrings() + required init?(coder: NSCoder) { + self.viewModel = DownloadListViewModel() + super.init(coder: coder) + } + override func loadView() { // swiftlint:disable:this function_body_length + view = NSView() + + view.addSubview(titleLabel) + view.addSubview(openDownloadsFolderButton) + view.addSubview(clearDownloadsButton) + view.addSubview(scrollView) + + titleLabel.isSelectable = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical) + titleLabel.setContentHuggingPriority(.init(rawValue: 251), for: .horizontal) + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.drawsBackground = false + titleLabel.font = .preferredFont(forTextStyle: .title3) + titleLabel.textColor = .labelColor + + openDownloadsFolderButton.translatesAutoresizingMaskIntoConstraints = false + openDownloadsFolderButton.alignment = .center + openDownloadsFolderButton.bezelStyle = .shadowlessSquare + openDownloadsFolderButton.isBordered = false + openDownloadsFolderButton.imagePosition = .imageOnly + openDownloadsFolderButton.imageScaling = .scaleProportionallyDown openDownloadsFolderButton.toolTip = UserText.openDownloadsFolderTooltip + openDownloadsFolderButton.cornerRadius = 4 + openDownloadsFolderButton.backgroundInset = CGPoint(x: 2, y: 2) + openDownloadsFolderButton.normalTintColor = .button + openDownloadsFolderButton.mouseDownColor = .buttonMouseDown + openDownloadsFolderButton.mouseOverColor = .buttonMouseOver + + clearDownloadsButton.translatesAutoresizingMaskIntoConstraints = false + clearDownloadsButton.alignment = .center + clearDownloadsButton.bezelStyle = .shadowlessSquare + clearDownloadsButton.isBordered = false + clearDownloadsButton.imagePosition = .imageOnly + clearDownloadsButton.imageScaling = .scaleProportionallyDown clearDownloadsButton.toolTip = UserText.clearDownloadHistoryTooltip + clearDownloadsButton.cornerRadius = 4 + clearDownloadsButton.backgroundInset = CGPoint(x: 2, y: 2) + clearDownloadsButton.normalTintColor = .button + clearDownloadsButton.mouseDownColor = .buttonMouseDown + clearDownloadsButton.mouseOverColor = .buttonMouseOver + + scrollView.autohidesScrollers = true + scrollView.borderType = .noBorder + scrollView.hasHorizontalScroller = false + scrollView.translatesAutoresizingMaskIntoConstraints = false + scrollView.usesPredominantAxisScrolling = false + scrollView.automaticallyAdjustsContentInsets = false + + let clipView = NSClipView() + clipView.documentView = tableView + + clipView.autoresizingMask = [.width, .height] + clipView.drawsBackground = false + clipView.frame = CGRect(x: 0, y: 0, width: 420, height: 440) + + tableView.addTableColumn(NSTableColumn()) + + tableView.headerView = nil + tableView.backgroundColor = .clear + tableView.gridColor = .clear + tableView.style = .fullWidth + tableView.rowHeight = 60 + tableView.setContentHuggingPriority(.defaultHigh, for: .vertical) + tableView.allowsMultipleSelection = false + tableView.doubleAction = #selector(DownloadsViewController.doubleClickAction) + tableView.target = self + tableView.delegate = self + tableView.dataSource = self + tableView.menu = setUpContextMenu() + + scrollView.contentView = clipView + + let separator = NSBox() + separator.boxType = .separator + separator.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(separator) + + setupLayout(separator: separator) + } + + private func setupLayout(separator: NSBox) { + tableViewHeightConstraint = scrollView.heightAnchor.constraint(equalToConstant: 440) + + NSLayoutConstraint.activate([ + titleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 12), + titleLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 12), + + openDownloadsFolderButton.widthAnchor.constraint(equalToConstant: 32), + openDownloadsFolderButton.heightAnchor.constraint(equalToConstant: 32), + openDownloadsFolderButton.leadingAnchor.constraint(greaterThanOrEqualTo: titleLabel.trailingAnchor, constant: 8), + openDownloadsFolderButton.centerYAnchor.constraint(equalTo: titleLabel.centerYAnchor), + + clearDownloadsButton.widthAnchor.constraint(equalToConstant: 32), + clearDownloadsButton.heightAnchor.constraint(equalToConstant: 32), + clearDownloadsButton.leadingAnchor.constraint(equalTo: openDownloadsFolderButton.trailingAnchor), + view.trailingAnchor.constraint(equalTo: clearDownloadsButton.trailingAnchor, constant: 11), + clearDownloadsButton.centerYAnchor.constraint(equalTo: openDownloadsFolderButton.centerYAnchor), + + view.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor), + view.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor), + scrollView.topAnchor.constraint(equalTo: view.topAnchor, constant: 44), + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + + separator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + separator.widthAnchor.constraint(equalTo: view.widthAnchor, constant: -2), + separator.topAnchor.constraint(equalTo: view.topAnchor, constant: 43), + + tableViewHeightConstraint + ]) + } + + override func viewDidLoad() { + super.viewDidLoad() preferredContentSize = Self.preferredContentSize + setupDragAndDrop() } override func viewWillAppear() { @@ -116,16 +215,21 @@ final class DownloadsViewController: NSViewController { downloadsCancellable = nil } - private func setUpStrings() { - titleLabel.stringValue = UserText.downloadsDialogTitle - openItem.title = UserText.downloadsOpenItem - showInFinderItem.title = UserText.downloadsShowInFinderItem - copyDownloadLinkItem.title = UserText.downloadsCopyLinkItem - openWebsiteItem.title = UserText.downloadsOpenWebsiteItem - removeFromListItem.title = UserText.downloadsRemoveFromListItem - stopItem.title = UserText.downloadsStopItem - restartItem.title = UserText.downloadsRestartItem - clearAllItem.title = UserText.downloadsClearAllItem + private func setUpContextMenu() -> NSMenu { + let menu = NSMenu { + NSMenuItem(title: UserText.downloadsOpenItem, action: #selector(openDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsShowInFinderItem, action: #selector(revealDownloadAction), target: self) + NSMenuItem.separator() + NSMenuItem(title: UserText.downloadsCopyLinkItem, action: #selector(copyDownloadLinkAction), target: self) + NSMenuItem(title: UserText.downloadsOpenWebsiteItem, action: #selector(openOriginatingWebsiteAction), target: self) + NSMenuItem.separator() + NSMenuItem(title: UserText.downloadsRemoveFromListItem, action: #selector(removeDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsStopItem, action: #selector(cancelDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsRestartItem, action: #selector(restartDownloadAction), target: self) + NSMenuItem(title: UserText.downloadsClearAllItem, action: #selector(clearDownloadsAction), target: self) + } + menu.delegate = self + return menu } private func index(for sender: Any) -> Int? { @@ -156,7 +260,7 @@ final class DownloadsViewController: NSViewController { // MARK: User Actions - @IBAction func openDownloadsFolderAction(_ sender: Any) { + @objc func openDownloadsFolderAction(_ sender: Any) { let prefs = DownloadsPreferences.shared let downloads = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask)[0] var url: URL? @@ -197,30 +301,23 @@ final class DownloadsViewController: NSViewController { self.dismiss() } - @IBAction func clearDownloadsAction(_ sender: Any) { + @objc func clearDownloadsAction(_ sender: Any) { viewModel.cleanupInactiveDownloads() self.dismiss() delegate?.clearDownloadsActionTriggered() } - @IBAction func openDownloadedFileAction(_ sender: Any) { - guard let index = index(for: sender), - let url = viewModel.items[safe: index]?.localURL - else { return } - NSWorkspace.shared.open(url) - } - - @IBAction func cancelDownloadAction(_ sender: Any) { + @objc func cancelDownloadAction(_ sender: Any) { guard let index = index(for: sender) else { return } viewModel.cancelDownload(at: index) } - @IBAction func removeDownloadAction(_ sender: Any) { + @objc func removeDownloadAction(_ sender: Any) { guard let index = index(for: sender) else { return } viewModel.removeDownload(at: index) } - @IBAction func revealDownloadAction(_ sender: Any) { + @objc func revealDownloadAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.localURL else { return } @@ -228,7 +325,7 @@ final class DownloadsViewController: NSViewController { NSWorkspace.shared.activateFileViewerSelecting([url]) } - func openDownloadAction(_ sender: Any) { + @objc func openDownloadAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.localURL else { return } @@ -236,12 +333,12 @@ final class DownloadsViewController: NSViewController { NSWorkspace.shared.open(url) } - @IBAction func restartDownloadAction(_ sender: Any) { + @objc func restartDownloadAction(_ sender: Any) { guard let index = index(for: sender) else { return } viewModel.restartDownload(at: index) } - @IBAction func copyDownloadLinkAction(_ sender: Any) { + @objc func copyDownloadLinkAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.url else { return } @@ -249,7 +346,7 @@ final class DownloadsViewController: NSViewController { NSPasteboard.general.copy(url) } - @IBAction func openOriginatingWebsiteAction(_ sender: Any) { + @objc func openOriginatingWebsiteAction(_ sender: Any) { guard let index = index(for: sender), let url = viewModel.items[safe: index]?.websiteURL else { return } @@ -258,7 +355,7 @@ final class DownloadsViewController: NSViewController { WindowControllersManager.shared.show(url: url, source: .historyEntry, newTab: true) } - @IBAction func doubleClickAction(_ sender: Any) { + @objc func doubleClickAction(_ sender: Any) { if index(for: sender) != nil { openDownloadAction(sender) } else { @@ -287,7 +384,7 @@ extension DownloadsViewController: NSMenuDelegate { for menuItem in menu.items { switch menuItem.action { - case #selector(openDownloadedFileAction(_:)), + case #selector(openDownloadAction(_:)), #selector(revealDownloadAction(_:)): if case .complete(.some(let url)) = item.state, FileManager.default.fileExists(atPath: url.path) { @@ -330,20 +427,17 @@ extension DownloadsViewController: NSTableViewDataSource, NSTableViewDelegate { } func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? { - let identifier: NSUserInterfaceItemIdentifier if viewModel.items.isEmpty { - identifier = .noDownloadsCell + return tableView.makeView(withIdentifier: .init(NoDownloadsCellView.className()), owner: self) as? NoDownloadsCellView + ?? NoDownloadsCellView(identifier: .init(NoDownloadsCellView.className())) + } else if viewModel.items.indices.contains(row) { - identifier = .downloadCell + return tableView.makeView(withIdentifier: .init(DownloadsCellView.className()), owner: self) as? DownloadsCellView + ?? DownloadsCellView(identifier: .init(DownloadsCellView.className())) } else { - identifier = .openDownloadsCell + return tableView.makeView(withIdentifier: .init(OpenDownloadsCellView.className()), owner: self) as? OpenDownloadsCellView + ?? OpenDownloadsCellView(identifier: .init(OpenDownloadsCellView.className())) } - let cell = tableView.makeView(withIdentifier: identifier, owner: nil) - if identifier == .downloadCell { - cell?.menu = contextMenu - } - - return cell } func tableView(_ tableView: NSTableView, shouldSelectRow row: Int) -> Bool { @@ -388,27 +482,15 @@ extension DownloadsViewController: NSTableViewDataSource, NSTableViewDelegate { } -private extension NSUserInterfaceItemIdentifier { - static let downloadCell = NSUserInterfaceItemIdentifier(rawValue: "cell") - static let noDownloadsCell = NSUserInterfaceItemIdentifier(rawValue: "NoDownloads") - static let openDownloadsCell = NSUserInterfaceItemIdentifier(rawValue: "OpenDownloads") -} - -final class NoDownloadViewCell: NSTableCellView { - @IBOutlet weak var openFolderButton: LinkButton! - @IBOutlet weak var titleLabel: NSTextField! - - override func awakeFromNib() { - titleLabel.stringValue = UserText.downloadsNoRecentDownload - openFolderButton.title = UserText.downloadsOpenDownloadsFolder - } -} - -final class OpenDownloadViewCell: NSTableCellView { - @IBOutlet weak var openFolderButton: LinkButton! +#if DEBUG +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: DownloadsViewController.preferredContentSize.width, height: DownloadsViewController.preferredContentSize.height)) { { - override func awakeFromNib() { - openFolderButton.title = UserText.downloadsOpenDownloadsFolder + let store = DownloadListStoreMock() + store.fetchBlock = { completion in + completion(.success(previewDownloadListItems)) } - -} + let viewModel = DownloadListViewModel(coordinator: DownloadListCoordinator(store: store)) + return DownloadsViewController(viewModel: viewModel) +}() } +#endif diff --git a/DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift b/DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift new file mode 100644 index 0000000000..9796154c32 --- /dev/null +++ b/DuckDuckGo/FileDownload/View/NoDownloadsCellView.swift @@ -0,0 +1,82 @@ +// +// NoDownloadsCellView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +final class NoDownloadsCellView: NSTableCellView { + + fileprivate enum Constants { + static let width: CGFloat = 420 + static let height: CGFloat = 60 + } + + private let titleLabel = NSTextField(string: UserText.downloadsNoRecentDownload) + private let openFolderButton = LinkButton(title: UserText.downloadsOpenDownloadsFolder, + target: nil, + action: #selector(DownloadsViewController.openDownloadsFolderAction)) + + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: CGRect(x: 0, y: 0, width: Constants.width, height: Constants.height)) + self.identifier = identifier + + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + private func setupUI() { + addSubview(titleLabel) + addSubview(openFolderButton) + + titleLabel.translatesAutoresizingMaskIntoConstraints = false + titleLabel.isEditable = false + titleLabel.isBordered = false + titleLabel.isSelectable = false + titleLabel.drawsBackground = false + titleLabel.font = .systemFont(ofSize: 13) + titleLabel.textColor = .secondaryLabelColor + titleLabel.lineBreakMode = .byTruncatingMiddle + + openFolderButton.translatesAutoresizingMaskIntoConstraints = false + openFolderButton.bezelStyle = .shadowlessSquare + openFolderButton.isBordered = false + openFolderButton.alignment = .center + openFolderButton.font = .systemFont(ofSize: 13) + openFolderButton.contentTintColor = .linkColor + + NSLayoutConstraint.activate([ + titleLabel.topAnchor.constraint(equalTo: topAnchor, constant: 12), + titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), + + openFolderButton.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, + constant: 4), + openFolderButton.centerXAnchor.constraint(equalTo: centerXAnchor), + ]) + } + +} + +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: NoDownloadsCellView.Constants.width, + height: NoDownloadsCellView.Constants.height)) { + PreviewViewController(showWindowTitle: false) { + NoDownloadsCellView(identifier: .init("")) + } +} diff --git a/DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift b/DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift new file mode 100644 index 0000000000..fbfed592da --- /dev/null +++ b/DuckDuckGo/FileDownload/View/OpenDownloadsCellView.swift @@ -0,0 +1,67 @@ +// +// OpenDownloadsCellView.swift +// +// Copyright © 2024 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import AppKit + +final class OpenDownloadsCellView: NSTableCellView { + + fileprivate enum Constants { + static let width: CGFloat = 420 + static let height: CGFloat = 60 + } + + private let openFolderButton = LinkButton(title: UserText.downloadsOpenDownloadsFolder, + target: nil, + action: #selector(DownloadsViewController.openDownloadsFolderAction)) + + init(identifier: NSUserInterfaceItemIdentifier) { + super.init(frame: CGRect(x: 0, y: 0, width: Constants.width, height: Constants.height)) + self.identifier = identifier + + setupUI() + } + + required init?(coder: NSCoder) { + fatalError("\(Self.self): Bad initializer") + } + + private func setupUI() { + addSubview(openFolderButton) + + openFolderButton.translatesAutoresizingMaskIntoConstraints = false + openFolderButton.bezelStyle = .shadowlessSquare + openFolderButton.isBordered = false + openFolderButton.alignment = .center + openFolderButton.font = .systemFont(ofSize: 13) + openFolderButton.contentTintColor = .linkColor + + NSLayoutConstraint.activate([ + openFolderButton.centerYAnchor.constraint(equalTo: centerYAnchor), + openFolderButton.centerXAnchor.constraint(equalTo: centerXAnchor), + ]) + } + +} + +@available(macOS 14.0, *) +#Preview(traits: .fixedLayout(width: OpenDownloadsCellView.Constants.width, + height: OpenDownloadsCellView.Constants.height)) { + PreviewViewController(showWindowTitle: false) { + OpenDownloadsCellView(identifier: .init("")) + } +} diff --git a/DuckDuckGo/Localizable.xcstrings b/DuckDuckGo/Localizable.xcstrings index 87e0f9dfb4..04f8d61b80 100644 --- a/DuckDuckGo/Localizable.xcstrings +++ b/DuckDuckGo/Localizable.xcstrings @@ -14664,66 +14664,6 @@ } } }, - "downloads.restart.item" : { - "comment" : "Contextual menu item in downloads manager to restart the download", - "extractionState" : "extracted_with_value", - "localizations" : { - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stopp" - } - }, - "en" : { - "stringUnit" : { - "state" : "new", - "value" : "Stop" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Detener" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Arrêter" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Interrompi" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Stoppen" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zatrzymaj" - } - }, - "pt" : { - "stringUnit" : { - "state" : "translated", - "value" : "Parar" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Остановить" - } - } - } - }, "downloads.show-in-finder.item" : { "comment" : "Contextual menu item in downloads manager to show the downloaded file in Finder", "extractionState" : "extracted_with_value", From fd879bf9e392e5db4583c7349ef144135fee1ebe Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Apr 2024 17:28:50 +0200 Subject: [PATCH 29/40] Fix lottie high Windowserver load (#2595) Task/Issue URL: https://app.asana.com/0/1177771139624306/1207024603216659/f **Description**: Force use of .mainThread to prevent high WindowServer Usage (Pending Fix with newer Lottie versions) --- DuckDuckGo/Application/AppDelegate.swift | 6 ++++++ .../View/AddressBarButtonsViewController.swift | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 22cc5ea79e..53ac49ff5b 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -222,6 +222,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { PrivacyFeatures.httpsUpgrade.loadDataAsync() bookmarksManager.loadBookmarks() + + // Force use of .mainThread to prevent high WindowServer Usage + // Pending Fix with newer Lottie versions + // https://app.asana.com/0/1177771139624306/1207024603216659/f + LottieConfiguration.shared.renderingEngine = .mainThread + if case .normal = NSApp.runType { FaviconManager.shared.loadFavicons() } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 7bb06c097f..148900fcec 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -553,7 +553,13 @@ final class AddressBarButtonsViewController: NSViewController { } private func setupAnimationViews() { - func addAndLayoutAnimationViewIfNeeded(animationView: LottieAnimationView?, animationName: String, renderingEngine: Lottie.RenderingEngineOption = .automatic) -> LottieAnimationView { + + func addAndLayoutAnimationViewIfNeeded(animationView: LottieAnimationView?, + animationName: String, + // Default use of .mainThread to prevent high WindowServer Usage + // Pending Fix with newer Lottie versions + // https://app.asana.com/0/1177771139624306/1207024603216659/f + renderingEngine: Lottie.RenderingEngineOption = .mainThread) -> LottieAnimationView { if let animationView = animationView, animationView.identifier?.rawValue == animationName { return animationView } From 69680b0ea495f7c5d6fe7c2866e4cdd6f71fd80b Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Wed, 10 Apr 2024 18:34:47 +0200 Subject: [PATCH 30/40] Fix VPN bug: Nearest city breaks register requests (#2589) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Task/Issue URL: https://app.asana.com/0/1203137811378537/1207044169885477/f **Description**: Due to a bug in the VPNLocationViewModel, we were using the string “Nearest” as a city ID which ended up making it to the `register` request. This only happens when we explicitly select Nearest rather than just selecting the country (which has the same effect). **Steps to test this PR**: 1. Make sure you’re hitting the production VPN environment (so you get the full countries + cities). 2. Go to VPN Location screen 3. Go to a country with multiple cities (currently only the US) and select any specific city 4. Now select Nearest (explicitly using the drop-down) 5. Go to the VPN management popover (e.g nav bar → … → VPN) and connect **Expected** VPN connects to the nearest city in that country --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- .../BothAppTargets/VPNLocation/VPNLocationViewModel.swift | 3 ++- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 2bbc99a10b..43f4bd6b34 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14448,7 +14448,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 133.0.0; + version = "133.0.0-1"; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 46c657c96d..8ab89dd501 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" : "c0b0cb55e7ac2f69d10452e1a5c06713155d798e", - "version" : "133.0.0" + "revision" : "ab719a6a786a3c4e898496c13ca76a470e89893b", + "version" : "133.0.0-1" } }, { diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift index 01a39e35f4..3368da5066 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationViewModel.swift @@ -80,7 +80,8 @@ final class VPNLocationViewModel: ObservableObject { func onCountryItemSelection(id: String, cityId: String? = nil) async { DailyPixel.fire(pixel: .networkProtectionGeoswitchingSetCustom, frequency: .dailyAndCount) - let location = NetworkProtectionSelectedLocation(country: id, city: cityId) + let city = cityId == VPNCityItemModel.nearest.id ? nil : cityId + let location = NetworkProtectionSelectedLocation(country: id, city: city) selectedLocation = .location(location) await reloadList() } diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 46c094664b..78a7506d5e 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0-1"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index d0ea0235c3..9667eea790 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0-1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), .package(path: "../LoginItems"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 556d4f308e..808b065230 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.0-1"), .package(path: "../SwiftUIExtensions") ], targets: [ From 7b1103ab2bd6a852b2d841ef41e5316a95383c8a Mon Sep 17 00:00:00 2001 From: Graeme Arthur Date: Wed, 10 Apr 2024 19:22:44 +0200 Subject: [PATCH 31/40] BSK release 133.1.0 (#2597) Task/Issue URL: https://app.asana.com/0/414235014887631/1207044067978093/f **Description**: **Steps to test this PR**: 1. --- ###### Internal references: [Pull Request Review Checklist](https://app.asana.com/0/1202500774821704/1203764234894239/f) [Software Engineering Expectations](https://app.asana.com/0/59792373528535/199064865822552) [Technical Design Template](https://app.asana.com/0/59792373528535/184709971311943) [Pull Request Documentation](https://app.asana.com/0/1202500774821704/1204012835277482/f) --- DuckDuckGo.xcodeproj/project.pbxproj | 2 +- .../project.xcworkspace/xcshareddata/swiftpm/Package.resolved | 4 ++-- LocalPackages/DataBrokerProtection/Package.swift | 2 +- LocalPackages/NetworkProtectionMac/Package.swift | 2 +- LocalPackages/SubscriptionUI/Package.swift | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 415aeb2e28..d002b6758e 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -14496,7 +14496,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 133.0.1; + version = 133.1.0; }; }; 9FF521422BAA8FF300B9819B /* XCRemoteSwiftPackageReference "lottie-spm" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a575d944d1..517700a5d4 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -32,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "39d74829150a9ecffea2f503c01851e54eda8ad1", - "version" : "133.0.1" + "revision" : "4699a5ff3d0669736e87f6da808884f245d80ede", + "version" : "133.1.0" } }, { diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index 9418e01778..1c0d592717 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.1.0"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper"), diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 4e62507c16..08e64e721c 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -31,7 +31,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.1.0"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions"), .package(path: "../LoginItems"), diff --git a/LocalPackages/SubscriptionUI/Package.swift b/LocalPackages/SubscriptionUI/Package.swift index 1c064eb962..c5720e3b75 100644 --- a/LocalPackages/SubscriptionUI/Package.swift +++ b/LocalPackages/SubscriptionUI/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["SubscriptionUI"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.0.1"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "133.1.0"), .package(path: "../SwiftUIExtensions") ], targets: [ From b44fa7f7824793d5c8edb74395a6927da762c0f9 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Thu, 11 Apr 2024 03:04:07 +0000 Subject: [PATCH 32/40] Bump version to 1.83.0 (158) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index eb40c52354..9cff20393f 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 157 +CURRENT_PROJECT_VERSION = 158 From 01d4705fc2ee2ecc189b31a65f06ca061df063e5 Mon Sep 17 00:00:00 2001 From: Sam Symons Date: Wed, 10 Apr 2024 21:35:26 -0700 Subject: [PATCH 33/40] [Release PR] Fix lottie high Windowserver load (#2598) Task/Issue URL: https://app.asana.com/0/1177771139624306/1207024603216659/f Description: This PR cherry pick's @afterxleep's changes to fix Lottie CPU usage. (#2595) --- DuckDuckGo/Application/AppDelegate.swift | 6 ++++++ .../View/AddressBarButtonsViewController.swift | 8 +++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 22cc5ea79e..53ac49ff5b 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -222,6 +222,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate { PrivacyFeatures.httpsUpgrade.loadDataAsync() bookmarksManager.loadBookmarks() + + // Force use of .mainThread to prevent high WindowServer Usage + // Pending Fix with newer Lottie versions + // https://app.asana.com/0/1177771139624306/1207024603216659/f + LottieConfiguration.shared.renderingEngine = .mainThread + if case .normal = NSApp.runType { FaviconManager.shared.loadFavicons() } diff --git a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift index 7afc044118..8d28152841 100644 --- a/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift +++ b/DuckDuckGo/NavigationBar/View/AddressBarButtonsViewController.swift @@ -553,7 +553,13 @@ final class AddressBarButtonsViewController: NSViewController { } private func setupAnimationViews() { - func addAndLayoutAnimationViewIfNeeded(animationView: LottieAnimationView?, animationName: String, renderingEngine: Lottie.RenderingEngineOption = .automatic) -> LottieAnimationView { + + func addAndLayoutAnimationViewIfNeeded(animationView: LottieAnimationView?, + animationName: String, + // Default use of .mainThread to prevent high WindowServer Usage + // Pending Fix with newer Lottie versions + // https://app.asana.com/0/1177771139624306/1207024603216659/f + renderingEngine: Lottie.RenderingEngineOption = .mainThread) -> LottieAnimationView { if let animationView = animationView, animationView.identifier?.rawValue == animationName { return animationView } From 33331a1093c82a881bb3250e4f2a35adde422d85 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Thu, 11 Apr 2024 05:13:12 +0000 Subject: [PATCH 34/40] Bump version to 1.83.0 (159) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 9cff20393f..7ac22f18ce 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 158 +CURRENT_PROJECT_VERSION = 159 From bf6d92e62e7c32c3e6f9d77fc7e0dadcd4b4824e Mon Sep 17 00:00:00 2001 From: Fernando Bunn Date: Thu, 11 Apr 2024 11:09:27 +0100 Subject: [PATCH 35/40] Update copy for DBP open button (#2586) Task/Issue URL: https://app.asana.com/0/1204167627774280/1207034788876871/f **Description**: Update copy for DBP open button --- .../SubscriptionUI/Sources/SubscriptionUI/UserText.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift index fed703c3f7..645253c3a4 100644 --- a/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift +++ b/LocalPackages/SubscriptionUI/Sources/SubscriptionUI/UserText.swift @@ -30,7 +30,7 @@ enum UserText { static let personalInformationRemovalServiceTitle = NSLocalizedString("subscription.preferences.services.personal.information.removal.title", value: "Personal Information Removal", comment: "Title for the Personal Information Removal service listed in the subscription preferences pane") static let personalInformationRemovalServiceDescription = NSLocalizedString("subscription.preferences.services.personal.information.removal.description", value: "Find and remove your personal information from sites that store and sell it.", comment: "Description for the Personal Information Removal service listed in the subscription preferences pane") - static let personalInformationRemovalServiceButtonTitle = NSLocalizedString("subscription.preferences.services.personal.information.removal.button.title", value: "Get Started", comment: "Title for the Personal Information Removal service button to open its settings") + static let personalInformationRemovalServiceButtonTitle = NSLocalizedString("subscription.preferences.services.personal.information.removal.button.title", value: "Open", comment: "Title for the Personal Information Removal service button to open its settings") static let identityTheftRestorationServiceTitle = NSLocalizedString("subscription.preferences.services.identity.theft.restoration.title", value: "Identity Theft Restoration", comment: "Title for the Identity Theft Restoration service listed in the subscription preferences pane") static let identityTheftRestorationServiceDescription = NSLocalizedString("subscription.preferences.services.identity.theft.restoration.description", value: "Restore stolen accounts and financial losses in the event of identity theft.", comment: "Description for the Identity Theft Restoration service listed in the subscription preferences pane") From 99b8fab97c4e02a96bdc9b04416f8382efd89023 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 11 Apr 2024 16:32:34 +0200 Subject: [PATCH 36/40] Automatically mark / close stale PRs (#2596) Task/Issue URL: https://app.asana.com/0/414709148257752/1207048535807038/f **Description**: - Marks inactive PRs as 'stale' after 7 days - Closes inactive PRs after 14 days --- .github/workflows/stale_pr.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/stale_pr.yml diff --git a/.github/workflows/stale_pr.yml b/.github/workflows/stale_pr.yml new file mode 100644 index 0000000000..a5590c9c63 --- /dev/null +++ b/.github/workflows/stale_pr.yml @@ -0,0 +1,19 @@ +name: Close Stale Pull Requests + +on: + schedule: + - cron: '0 0 * * *' + +jobs: + close_stale_prs: + runs-on: ubuntu-latest + steps: + - name: Close stale pull requests + uses: actions/stale@v9 + with: + stale-pr-message: 'This PR has been inactive for more than 7 days and will be automatically closed 7 days from now.' + days-before-stale: 7 + close-pr-message: 'This PR has been closed after 14 days of inactivity. Feel free to reopen it if you plan to continue working on it or have further discussions.' + days-before-close: 7 + stale-pr-label: stale + exempt-draft-pr: true \ No newline at end of file From b5796b624536c73d6f58d81f30ba907be80a6813 Mon Sep 17 00:00:00 2001 From: Anh Do <18567+quanganhdo@users.noreply.github.com> Date: Thu, 11 Apr 2024 17:28:20 -0400 Subject: [PATCH 37/40] Fix popover not displayed reliably when VPN shortcut is unpinned (#2606) Task/Issue URL: https://app.asana.com/0/1203137811378537/1207059171151302/f Tech Design URL: CC: Description: When the VPN shortcut is unpinned & the user tries to open the popover from the More Options menu, the temporarily shown VPN shortcut is hidden prematurely and (?) cause the popover to be closed right away. --- DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift index 0274af6b08..8c99ed1284 100644 --- a/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift +++ b/DuckDuckGo/NavigationBar/View/NavigationBarViewController.swift @@ -973,6 +973,7 @@ extension NavigationBarViewController: NSMenuDelegate { .store(in: &cancellables) networkProtectionButtonModel.$showButton + .removeDuplicates() .receive(on: RunLoop.main) .sink { [weak self] show in let isPopUpWindow = self?.view.window?.isPopUpWindow ?? false From ad5947f4119b0c9bacc59afc21b7954a144c2f99 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 11 Apr 2024 23:51:05 +0200 Subject: [PATCH 38/40] macOS VPN: Ask users to reboot if system extension was not uninstalled (#2603) Task/Issue URL: https://app.asana.com/0/1199230911884351/1207056555335340/f ## Description We now ask again users to reboot if macOS requires it to complete installation of the system extension. --- .../Common/Localizables/UserText+NetworkProtection.swift | 2 +- .../NetworkProtectionPixelEvent.swift | 8 ++++---- .../NetworkProtectionTunnelController.swift | 6 +++--- DuckDuckGoVPN/NetworkExtensionController.swift | 2 +- .../NetworkProtectionPixelEventTests.swift | 6 ++++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift index 69d36bb228..2a5ec04f2d 100644 --- a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift +++ b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift @@ -67,7 +67,7 @@ extension UserText { // "network.protection.system.extension.unknown.activation.error" - Message shown to users when they try to enable NetP and there is an unexpected activation error. static let networkProtectionUnknownActivationError = "There as an unexpected error. Please try again." // "network.protection.system.extension.please.reboot" - Message shown to users when they try to enable NetP and they need to reboot the computer to complete the installation - static let networkProtectionPleaseReboot = "Please reboot to activate the VPN" + static let networkProtectionPleaseReboot = "VPN update available. Restart your Mac to reconnect." } // MARK: - VPN Waitlist diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index 890f942b93..d1797d0e0c 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -85,7 +85,7 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { case networkProtectionRekeyCompleted case networkProtectionRekeyFailure(_ error: Error) - case networkProtectionSystemExtensionActivationFailure + case networkProtectionSystemExtensionActivationFailure(_ error: Error) case networkProtectionUnhandledError(function: String, line: Int, error: Error) @@ -393,8 +393,7 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionWireguardErrorCannotStartWireguardBackend, .networkProtectionNoAuthTokenFoundError, .networkProtectionRekeyAttempt, - .networkProtectionRekeyCompleted, - .networkProtectionSystemExtensionActivationFailure: + .networkProtectionRekeyCompleted: return nil case .networkProtectionClientFailedToRedeemInviteCode(let error), .networkProtectionClientFailedToFetchLocations(let error), @@ -408,7 +407,8 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionClientFailedToParseRedeemResponse(let error), .networkProtectionWireguardErrorCannotSetNetworkSettings(let error), .networkProtectionRekeyFailure(let error), - .networkProtectionUnhandledError(_, _, let error): + .networkProtectionUnhandledError(_, _, let error), + .networkProtectionSystemExtensionActivationFailure(let error): return error } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index e42b917710..911913504f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -418,16 +418,16 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr controllerErrorStore.lastErrorMessage = UserText.networkProtectionSystemSettings case SystemExtensionRequestError.unknownRequestResult: controllerErrorStore.lastErrorMessage = UserText.networkProtectionUnknownActivationError - case SystemExtensionRequestError.willActivateAfterReboot: + case OSSystemExtensionError.extensionNotFound, + SystemExtensionRequestError.willActivateAfterReboot: controllerErrorStore.lastErrorMessage = UserText.networkProtectionPleaseReboot default: controllerErrorStore.lastErrorMessage = error.localizedDescription } PixelKit.fire( - NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure, + NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure(error), frequency: .standard, - withError: error, includeAppVersionParameter: true ) diff --git a/DuckDuckGoVPN/NetworkExtensionController.swift b/DuckDuckGoVPN/NetworkExtensionController.swift index d850bfbb80..23a5eca958 100644 --- a/DuckDuckGoVPN/NetworkExtensionController.swift +++ b/DuckDuckGoVPN/NetworkExtensionController.swift @@ -54,7 +54,7 @@ extension NetworkExtensionController { NetworkProtectionLastVersionRunStore(userDefaults: defaults).lastExtensionVersionRun = extensionVersion - try? await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) + try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) #endif } diff --git a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift index 43ccdb22ab..a2209cd64b 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift +++ b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift @@ -303,8 +303,10 @@ final class NetworkProtectionPixelEventTests: XCTestCase { underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) - fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure, - and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure"), + fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure(TestError.testError), + and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure", + error: TestError.testError, + underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionUnhandledError(function: "function", line: 1, error: TestError.testError), From 53bcda32d8e59e889084bb81cc14dbaf95aa3c55 Mon Sep 17 00:00:00 2001 From: Diego Rey Mendez Date: Thu, 11 Apr 2024 23:51:05 +0200 Subject: [PATCH 39/40] macOS VPN: Ask users to reboot if system extension was not uninstalled (#2603) Task/Issue URL: https://app.asana.com/0/1199230911884351/1207056555335340/f ## Description We now ask again users to reboot if macOS requires it to complete installation of the system extension. --- .../Common/Localizables/UserText+NetworkProtection.swift | 2 +- .../NetworkProtectionPixelEvent.swift | 8 ++++---- .../NetworkProtectionTunnelController.swift | 6 +++--- DuckDuckGoVPN/NetworkExtensionController.swift | 2 +- .../NetworkProtectionPixelEventTests.swift | 6 ++++-- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift index 69d36bb228..2a5ec04f2d 100644 --- a/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift +++ b/DuckDuckGo/Common/Localizables/UserText+NetworkProtection.swift @@ -67,7 +67,7 @@ extension UserText { // "network.protection.system.extension.unknown.activation.error" - Message shown to users when they try to enable NetP and there is an unexpected activation error. static let networkProtectionUnknownActivationError = "There as an unexpected error. Please try again." // "network.protection.system.extension.please.reboot" - Message shown to users when they try to enable NetP and they need to reboot the computer to complete the installation - static let networkProtectionPleaseReboot = "Please reboot to activate the VPN" + static let networkProtectionPleaseReboot = "VPN update available. Restart your Mac to reconnect." } // MARK: - VPN Waitlist diff --git a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift index 890f942b93..d1797d0e0c 100644 --- a/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift +++ b/DuckDuckGo/NetworkProtection/AppAndExtensionAndAgentTargets/NetworkProtectionPixelEvent.swift @@ -85,7 +85,7 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { case networkProtectionRekeyCompleted case networkProtectionRekeyFailure(_ error: Error) - case networkProtectionSystemExtensionActivationFailure + case networkProtectionSystemExtensionActivationFailure(_ error: Error) case networkProtectionUnhandledError(function: String, line: Int, error: Error) @@ -393,8 +393,7 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionWireguardErrorCannotStartWireguardBackend, .networkProtectionNoAuthTokenFoundError, .networkProtectionRekeyAttempt, - .networkProtectionRekeyCompleted, - .networkProtectionSystemExtensionActivationFailure: + .networkProtectionRekeyCompleted: return nil case .networkProtectionClientFailedToRedeemInviteCode(let error), .networkProtectionClientFailedToFetchLocations(let error), @@ -408,7 +407,8 @@ enum NetworkProtectionPixelEvent: PixelKitEventV2 { .networkProtectionClientFailedToParseRedeemResponse(let error), .networkProtectionWireguardErrorCannotSetNetworkSettings(let error), .networkProtectionRekeyFailure(let error), - .networkProtectionUnhandledError(_, _, let error): + .networkProtectionUnhandledError(_, _, let error), + .networkProtectionSystemExtensionActivationFailure(let error): return error } } diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift index e42b917710..911913504f 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/NetworkProtectionTunnelController.swift @@ -418,16 +418,16 @@ final class NetworkProtectionTunnelController: TunnelController, TunnelSessionPr controllerErrorStore.lastErrorMessage = UserText.networkProtectionSystemSettings case SystemExtensionRequestError.unknownRequestResult: controllerErrorStore.lastErrorMessage = UserText.networkProtectionUnknownActivationError - case SystemExtensionRequestError.willActivateAfterReboot: + case OSSystemExtensionError.extensionNotFound, + SystemExtensionRequestError.willActivateAfterReboot: controllerErrorStore.lastErrorMessage = UserText.networkProtectionPleaseReboot default: controllerErrorStore.lastErrorMessage = error.localizedDescription } PixelKit.fire( - NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure, + NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure(error), frequency: .standard, - withError: error, includeAppVersionParameter: true ) diff --git a/DuckDuckGoVPN/NetworkExtensionController.swift b/DuckDuckGoVPN/NetworkExtensionController.swift index d850bfbb80..23a5eca958 100644 --- a/DuckDuckGoVPN/NetworkExtensionController.swift +++ b/DuckDuckGoVPN/NetworkExtensionController.swift @@ -54,7 +54,7 @@ extension NetworkExtensionController { NetworkProtectionLastVersionRunStore(userDefaults: defaults).lastExtensionVersionRun = extensionVersion - try? await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) + try await Task.sleep(nanoseconds: 300 * NSEC_PER_MSEC) #endif } diff --git a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift index 43ccdb22ab..a2209cd64b 100644 --- a/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift +++ b/UnitTests/NetworkProtection/NetworkProtectionPixelEventTests.swift @@ -303,8 +303,10 @@ final class NetworkProtectionPixelEventTests: XCTestCase { underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) - fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure, - and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure"), + fire(NetworkProtectionPixelEvent.networkProtectionSystemExtensionActivationFailure(TestError.testError), + and: .expect(pixelName: "m_mac_netp_system_extension_activation_failure", + error: TestError.testError, + underlyingErrors: [TestError.underlyingError]), file: #filePath, line: #line) fire(NetworkProtectionPixelEvent.networkProtectionUnhandledError(function: "function", line: 1, error: TestError.testError), From baad13ea74a594bdf9f03fe732e5137090d74f01 Mon Sep 17 00:00:00 2001 From: Dax the Duck Date: Thu, 11 Apr 2024 22:14:27 +0000 Subject: [PATCH 40/40] Bump version to 1.83.0 (160) --- Configuration/BuildNumber.xcconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Configuration/BuildNumber.xcconfig b/Configuration/BuildNumber.xcconfig index 7ac22f18ce..937777042c 100644 --- a/Configuration/BuildNumber.xcconfig +++ b/Configuration/BuildNumber.xcconfig @@ -1 +1 @@ -CURRENT_PROJECT_VERSION = 159 +CURRENT_PROJECT_VERSION = 160