diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 646cb581d2..a947680535 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -459,9 +459,20 @@ AAECA42024EEA4AC00EFA63A /* IndexPathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAECA41F24EEA4AC00EFA63A /* IndexPathExtension.swift */; }; AAEEC6A927088ADB008445F7 /* FireCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEEC6A827088ADB008445F7 /* FireCoordinator.swift */; }; AAEF6BC8276A081C0024DCF4 /* FaviconSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAEF6BC7276A081C0024DCF4 /* FaviconSelector.swift */; }; - AAF7D3862567CED500998667 /* WebViewConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAF7D3852567CED500998667 /* WebViewConfiguration.swift */; }; AAFCB37F25E545D400859DD4 /* PublisherExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFCB37E25E545D400859DD4 /* PublisherExtension.swift */; }; AAFE068326C7082D005434CC /* WebKitVersionProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AAFE068226C7082D005434CC /* WebKitVersionProvider.swift */; }; + B31055C427A1BA1D001AC618 /* AutoconsentUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31055BC27A1BA1D001AC618 /* AutoconsentUserScript.swift */; }; + B31055C527A1BA1D001AC618 /* autoconsent.html in Resources */ = {isa = PBXBuildFile; fileRef = B31055BD27A1BA1D001AC618 /* autoconsent.html */; }; + B31055C627A1BA1D001AC618 /* userscript.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055BE27A1BA1D001AC618 /* userscript.js */; }; + B31055C727A1BA1D001AC618 /* browser-shim.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055BF27A1BA1D001AC618 /* browser-shim.js */; }; + B31055C827A1BA1D001AC618 /* background-bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C027A1BA1D001AC618 /* background-bundle.js */; }; + B31055C927A1BA1D001AC618 /* AutoconsentBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31055C127A1BA1D001AC618 /* AutoconsentBackground.swift */; }; + B31055CA27A1BA1D001AC618 /* background.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C227A1BA1D001AC618 /* background.js */; }; + B31055CB27A1BA1D001AC618 /* autoconsent-bundle.js in Resources */ = {isa = PBXBuildFile; fileRef = B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */; }; + B31055CE27A1BA44001AC618 /* AutoconsentBackgroundTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B31055CD27A1BA44001AC618 /* AutoconsentBackgroundTests.swift */; }; + B3FB198E27BC013C00513DC1 /* autoconsent-test-page.html in Resources */ = {isa = PBXBuildFile; fileRef = B3FB198D27BC013C00513DC1 /* autoconsent-test-page.html */; }; + B3FB199027BC015600513DC1 /* autoconsent-test.js in Resources */ = {isa = PBXBuildFile; fileRef = B3FB198F27BC015600513DC1 /* autoconsent-test.js */; }; + B3FB199327BD0AD400513DC1 /* CookieConsentInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3FB199227BD0AD400513DC1 /* CookieConsentInfo.swift */; }; B6040856274B830F00680351 /* DictionaryExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6040855274B830F00680351 /* DictionaryExtension.swift */; }; B604085C274B8FBA00680351 /* UnprotectedDomains.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B604085A274B8CA300680351 /* UnprotectedDomains.xcdatamodeld */; }; B6085D062743905F00A9C456 /* CoreDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6085D052743905F00A9C456 /* CoreDataStore.swift */; }; @@ -475,6 +486,10 @@ B6106BB126A7D8720013B453 /* PermissionStoreTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BB026A7D8720013B453 /* PermissionStoreTests.swift */; }; B6106BB326A7F4AA0013B453 /* GeolocationServiceMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BB226A7F4AA0013B453 /* GeolocationServiceMock.swift */; }; B6106BB526A809E60013B453 /* GeolocationProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106BB426A809E60013B453 /* GeolocationProviderTests.swift */; }; + B610F2BB27A145C500FCEBE9 /* RulesCompilationMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */; }; + B610F2E427A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E327A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift */; }; + B610F2EB27AA8E4500FCEBE9 /* ContentBlockingUpdatingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E527AA388100FCEBE9 /* ContentBlockingUpdatingTests.swift */; }; + B610F2EC27AA8F9400FCEBE9 /* ContentBlockerRulesManagerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */; }; B61EF3EC266F91E700B4D78F /* WKWebView+Download.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61EF3EB266F91E700B4D78F /* WKWebView+Download.swift */; }; B61EF3F1266F922200B4D78F /* WKProcessPool+DownloadDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61EF3F0266F922200B4D78F /* WKProcessPool+DownloadDelegate.swift */; }; B61F015525EDD5A700ABB5A3 /* UserContentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B61F015425EDD5A700ABB5A3 /* UserContentController.swift */; }; @@ -1153,9 +1168,20 @@ AAECA41F24EEA4AC00EFA63A /* IndexPathExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexPathExtension.swift; sourceTree = ""; }; AAEEC6A827088ADB008445F7 /* FireCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FireCoordinator.swift; sourceTree = ""; }; AAEF6BC7276A081C0024DCF4 /* FaviconSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconSelector.swift; sourceTree = ""; }; - AAF7D3852567CED500998667 /* WebViewConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewConfiguration.swift; sourceTree = ""; }; AAFCB37E25E545D400859DD4 /* PublisherExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PublisherExtension.swift; sourceTree = ""; }; AAFE068226C7082D005434CC /* WebKitVersionProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebKitVersionProvider.swift; sourceTree = ""; }; + B31055BC27A1BA1D001AC618 /* AutoconsentUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoconsentUserScript.swift; path = Autoconsent/AutoconsentUserScript.swift; sourceTree = ""; }; + B31055BD27A1BA1D001AC618 /* autoconsent.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; name = autoconsent.html; path = Autoconsent/autoconsent.html; sourceTree = ""; }; + B31055BE27A1BA1D001AC618 /* userscript.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = userscript.js; path = Autoconsent/userscript.js; sourceTree = ""; }; + B31055BF27A1BA1D001AC618 /* browser-shim.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = "browser-shim.js"; path = "Autoconsent/browser-shim.js"; sourceTree = ""; }; + B31055C027A1BA1D001AC618 /* background-bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = "background-bundle.js"; path = "Autoconsent/background-bundle.js"; sourceTree = ""; }; + B31055C127A1BA1D001AC618 /* AutoconsentBackground.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoconsentBackground.swift; path = Autoconsent/AutoconsentBackground.swift; sourceTree = ""; }; + B31055C227A1BA1D001AC618 /* background.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = background.js; path = Autoconsent/background.js; sourceTree = ""; }; + B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.javascript; name = "autoconsent-bundle.js"; path = "Autoconsent/autoconsent-bundle.js"; sourceTree = ""; }; + B31055CD27A1BA44001AC618 /* AutoconsentBackgroundTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = AutoconsentBackgroundTests.swift; path = Autoconsent/AutoconsentBackgroundTests.swift; sourceTree = ""; }; + B3FB198D27BC013C00513DC1 /* autoconsent-test-page.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "autoconsent-test-page.html"; sourceTree = ""; }; + B3FB198F27BC015600513DC1 /* autoconsent-test.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = "autoconsent-test.js"; sourceTree = ""; }; + B3FB199227BD0AD400513DC1 /* CookieConsentInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieConsentInfo.swift; sourceTree = ""; }; B6040855274B830F00680351 /* DictionaryExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DictionaryExtension.swift; sourceTree = ""; }; B604085B274B8CA400680351 /* Permissions.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Permissions.xcdatamodel; sourceTree = ""; }; B6085D052743905F00A9C456 /* CoreDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataStore.swift; sourceTree = ""; }; @@ -1170,6 +1196,10 @@ B6106BB026A7D8720013B453 /* PermissionStoreTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionStoreTests.swift; sourceTree = ""; }; B6106BB226A7F4AA0013B453 /* GeolocationServiceMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeolocationServiceMock.swift; sourceTree = ""; }; B6106BB426A809E60013B453 /* GeolocationProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeolocationProviderTests.swift; sourceTree = ""; }; + B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RulesCompilationMonitor.swift; sourceTree = ""; }; + B610F2E327A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CBRCompileTimeReporterTests.swift; sourceTree = ""; }; + B610F2E527AA388100FCEBE9 /* ContentBlockingUpdatingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockingUpdatingTests.swift; sourceTree = ""; }; + B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentBlockerRulesManagerMock.swift; sourceTree = ""; }; B61EF3EB266F91E700B4D78F /* WKWebView+Download.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKWebView+Download.swift"; sourceTree = ""; }; B61EF3F0266F922200B4D78F /* WKProcessPool+DownloadDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WKProcessPool+DownloadDelegate.swift"; sourceTree = ""; }; B61F015425EDD5A700ABB5A3 /* UserContentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserContentController.swift; sourceTree = ""; }; @@ -1541,6 +1571,7 @@ 4B1AD89E25FC27E200261379 /* Integration Tests */ = { isa = PBXGroup; children = ( + B31055CC27A1BA39001AC618 /* Autoconsent */, 4B1AD91625FC46FB00261379 /* CoreDataEncryptionTests.swift */, 4BA1A6EA258C288C00F6F690 /* EncryptionKeyStoreTests.swift */, 4B1AD8A125FC27E200261379 /* Info.plist */, @@ -1794,6 +1825,8 @@ 9833913227AAAEEE00DAF119 /* EmbeddedTrackerDataTests.swift */, EA1E52B42798CF98002EC53C /* ClickToLoadModelTests.swift */, EA8AE769279FBDB20078943E /* ClickToLoadTDSTests.swift */, + B610F2E527AA388100FCEBE9 /* ContentBlockingUpdatingTests.swift */, + B610F2E727AA397100FCEBE9 /* ContentBlockerRulesManagerMock.swift */, ); path = ContentBlocker; sourceTree = ""; @@ -2356,6 +2389,7 @@ AA585D80248FD31100E9A3E2 /* DuckDuckGo */ = { isa = PBXGroup; children = ( + B31055BB27A1BA0E001AC618 /* Autoconsent */, B6A9E47526146A440067D1B9 /* API */, AA4D700525545EDE00C3411E /* AppDelegate */, AAC5E4C025D6A6A9007F5990 /* Bookmarks */, @@ -2735,7 +2769,6 @@ 85E11C2E25E7DC7E00974CAF /* ExternalURLHandler.swift */, AA9FF95824A1ECF20039E328 /* Tab.swift */, 85AC3AEE25D5CE9800C7D2AA /* UserScripts.swift */, - AAF7D3852567CED500998667 /* WebViewConfiguration.swift */, B61F015425EDD5A700ABB5A3 /* UserContentController.swift */, ); path = Model; @@ -3241,6 +3274,31 @@ path = View; sourceTree = ""; }; + B31055BB27A1BA0E001AC618 /* Autoconsent */ = { + isa = PBXGroup; + children = ( + B31055C327A1BA1D001AC618 /* autoconsent-bundle.js */, + B31055BD27A1BA1D001AC618 /* autoconsent.html */, + B31055C127A1BA1D001AC618 /* AutoconsentBackground.swift */, + B31055BC27A1BA1D001AC618 /* AutoconsentUserScript.swift */, + B31055C027A1BA1D001AC618 /* background-bundle.js */, + B31055C227A1BA1D001AC618 /* background.js */, + B31055BF27A1BA1D001AC618 /* browser-shim.js */, + B31055BE27A1BA1D001AC618 /* userscript.js */, + ); + name = Autoconsent; + sourceTree = ""; + }; + B31055CC27A1BA39001AC618 /* Autoconsent */ = { + isa = PBXGroup; + children = ( + B31055CD27A1BA44001AC618 /* AutoconsentBackgroundTests.swift */, + B3FB198D27BC013C00513DC1 /* autoconsent-test-page.html */, + B3FB198F27BC015600513DC1 /* autoconsent-test.js */, + ); + name = Autoconsent; + sourceTree = ""; + }; B6040859274B8C5200680351 /* Unprotected Domains */ = { isa = PBXGroup; children = ( @@ -3337,6 +3395,7 @@ B6106BA226A7BEA00013B453 /* PermissionAuthorizationState.swift */, AA9B7C7D26A06E040008D425 /* TrackerInfo.swift */, AA9B7C8226A197A00008D425 /* ServerTrust.swift */, + B3FB199227BD0AD400513DC1 /* CookieConsentInfo.swift */, ); path = Model; sourceTree = ""; @@ -3434,6 +3493,7 @@ B6A9E47E26146A800067D1B9 /* PixelArguments.swift */, B6A9E48326146AAB0067D1B9 /* PixelParameters.swift */, B6A9E4A2261475C70067D1B9 /* AppUsageActivityMonitor.swift */, + B610F2BA27A145C500FCEBE9 /* RulesCompilationMonitor.swift */, B6DA44012616B28300DD1EC2 /* PixelDataStore.swift */, B68C92C32750EF76002AC6B0 /* PixelDataRecord.swift */, B6DA44062616B30600DD1EC2 /* PixelDataModel.xcdatamodeld */, @@ -3492,6 +3552,7 @@ B6DA44272616CAE000DD1EC2 /* AppUsageActivityMonitorTests.swift */, B6DA441D2616C84600DD1EC2 /* PixelStoreMock.swift */, 4B117F7C276C0CB5002F3D8C /* LocalStatisticsStoreTests.swift */, + B610F2E327A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift */, ); path = Statistics; sourceTree = ""; @@ -3674,6 +3735,8 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B3FB199027BC015600513DC1 /* autoconsent-test.js in Resources */, + B3FB198E27BC013C00513DC1 /* autoconsent-test-page.html in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -3693,14 +3756,18 @@ AA693E5E2696E5B90007BB78 /* CrashReports.storyboard in Resources */, 9833913127AAA4B500DAF119 /* trackerData.json in Resources */, 4B0511CE262CAA5A00F6079C /* DownloadPreferencesTableCellView.xib in Resources */, + B31055CA27A1BA1D001AC618 /* background.js in Resources */, 8511E18425F82B34002F516B /* 01_Fire_really_small.json in Resources */, 85B7184A27677C2D00B4277F /* Onboarding.storyboard in Resources */, 4B0511C3262CAA5A00F6079C /* Preferences.storyboard in Resources */, EA477680272A21B700419EDA /* clickToLoadConfig.json in Resources */, B6B1E88226D5DAC30062C350 /* Downloads.storyboard in Resources */, AA3439712754D4E900B241FA /* dark-shield.json in Resources */, + B31055C827A1BA1D001AC618 /* background-bundle.js in Resources */, + B31055CB27A1BA1D001AC618 /* autoconsent-bundle.js in Resources */, 85A0117425AF2EDF00FA6A0C /* FindInPage.storyboard in Resources */, AA80EC89256C49B8007083E7 /* Localizable.strings in Resources */, + B31055C627A1BA1D001AC618 /* userscript.js in Resources */, EA4617F0273A28A700F110A2 /* fb-tds.json in Resources */, AAE8B102258A41C000E81239 /* Tooltip.storyboard in Resources */, AA68C3D72490F821001B8783 /* README.md in Resources */, @@ -3721,9 +3788,11 @@ AAE71E3825F7869300D74437 /* HomepageCollectionViewItem.xib in Resources */, AA3439792754D55100B241FA /* trackers-1.json in Resources */, AA34397C2754D55100B241FA /* dark-trackers-1.json in Resources */, + B31055C527A1BA1D001AC618 /* autoconsent.html in Resources */, 4B723E1126B0006C00E14D75 /* DataImport.storyboard in Resources */, 4B92929026670D1700AD2C21 /* BookmarkTableCellView.xib in Resources */, 339A6B5826A044BA00E3DAE8 /* duckduckgo-privacy-dashboard in Resources */, + B31055C727A1BA1D001AC618 /* browser-shim.js in Resources */, 4B92928E26670D1700AD2C21 /* BookmarkOutlineViewCell.xib in Resources */, 858C78FC2705EB5F009B2B44 /* HomepageHeader.xib in Resources */, B64C84DE2692D7400048FEBE /* PermissionAuthorization.storyboard in Resources */, @@ -3843,6 +3912,7 @@ files = ( B662D3DF275616FF0035D4D6 /* EncryptionKeyStoreMock.swift in Sources */, 4B1AD8E225FC390B00261379 /* EncryptionMocks.swift in Sources */, + B31055CE27A1BA44001AC618 /* AutoconsentBackgroundTests.swift in Sources */, 4B1AD91725FC46FB00261379 /* CoreDataEncryptionTests.swift in Sources */, 7BA4727D26F01BC400EAA165 /* CoreDataTestUtilities.swift in Sources */, 4B1AD92125FC474E00261379 /* CoreDataEncryptionTesting.xcdatamodeld in Sources */, @@ -3914,6 +3984,7 @@ 1430DFF524D0580F00B8978C /* TabBarViewController.swift in Sources */, 4B92929B26670D2A00AD2C21 /* BookmarkOutlineViewDataSource.swift in Sources */, 85D885B026A590A90077C374 /* NSNotificationName+PasswordManager.swift in Sources */, + B610F2BB27A145C500FCEBE9 /* RulesCompilationMonitor.swift in Sources */, AAC30A28268E045400D2D9CD /* CrashReportReader.swift in Sources */, 85AC3B3525DA82A600C7D2AA /* DataTaskProviding.swift in Sources */, AAEF6BC8276A081C0024DCF4 /* FaviconSelector.swift in Sources */, @@ -4115,6 +4186,8 @@ 4B8D9062276D1D880078DB17 /* LocaleExtension.swift in Sources */, AAFE068326C7082D005434CC /* WebKitVersionProvider.swift in Sources */, B63D467A25BFC3E100874977 /* NSCoderExtensions.swift in Sources */, + B31055C927A1BA1D001AC618 /* AutoconsentBackground.swift in Sources */, + B3FB199327BD0AD400513DC1 /* CookieConsentInfo.swift in Sources */, B6A5A27125B9377300AA7ADA /* StatePersistenceService.swift in Sources */, B68458B025C7E76A00DC17B6 /* WindowManager+StateRestoration.swift in Sources */, B68458C525C7EA0C00DC17B6 /* TabCollection+NSSecureCoding.swift in Sources */, @@ -4274,6 +4347,7 @@ B6CF78DE267B099C00CD4F13 /* WKNavigationActionExtension.swift in Sources */, AA7412B224D0B3AC00D22FE0 /* TabBarViewItem.swift in Sources */, 856C98D52570116900A22F1F /* NSWindow+Toast.swift in Sources */, + B31055C427A1BA1D001AC618 /* AutoconsentUserScript.swift in Sources */, 859E7D6B27453BF3009C2B69 /* BookmarksExporter.swift in Sources */, 4B5FF67826B602B100D42879 /* FirefoxDataImporter.swift in Sources */, 4B02198B25E05FAC00ED7DEA /* FireproofInfoViewController.swift in Sources */, @@ -4283,7 +4357,6 @@ 4B02198A25E05FAC00ED7DEA /* FireproofDomains.swift in Sources */, 4B677442255DBEEA00025BD8 /* Database.swift in Sources */, 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */, - AAF7D3862567CED500998667 /* WebViewConfiguration.swift in Sources */, B61F015525EDD5A700ABB5A3 /* UserContentController.swift in Sources */, 4BF01C00272AE74C00884A61 /* CountryList.swift in Sources */, AAC5E4D925D6A711007F5990 /* BookmarkStore.swift in Sources */, @@ -4379,6 +4452,7 @@ AA63745424C9BF9A00AB2AC4 /* SuggestionContainerTests.swift in Sources */, AAC9C01524CAFBCE00AD1325 /* TabTests.swift in Sources */, B69B504C2726CA2900758A2B /* MockVariantManager.swift in Sources */, + B610F2EC27AA8F9400FCEBE9 /* ContentBlockerRulesManagerMock.swift in Sources */, B6BBF1722744CE36004F850E /* FireproofDomainsStoreMock.swift in Sources */, 4BA1A6D9258C0CB300F6F690 /* DataEncryptionTests.swift in Sources */, EA1E52B52798CF98002EC53C /* ClickToLoadModelTests.swift in Sources */, @@ -4401,6 +4475,7 @@ 4BB99D1126FE1A84001E4761 /* SafariBookmarksReaderTests.swift in Sources */, 4B11060A25903EAC0039B979 /* CoreDataEncryptionTests.swift in Sources */, 4B9292C32667103100AD2C21 /* PasteboardBookmarkTests.swift in Sources */, + B610F2E427A8F37A00FCEBE9 /* CBRCompileTimeReporterTests.swift in Sources */, AAEC74BB2642E67C00C2EFBC /* NSPersistentContainerExtension.swift in Sources */, AABAF59C260A7D130085060C /* FaviconManagerMock.swift in Sources */, AAEC74B82642E43800C2EFBC /* HistoryStoreTests.swift in Sources */, @@ -4410,6 +4485,7 @@ 4B723E0526B0003E00E14D75 /* DataImportMocks.swift in Sources */, 85AC3B1725D9BC1A00C7D2AA /* ConfigurationDownloaderTests.swift in Sources */, B693955D26F19CD70015B914 /* DownloadListStoreTests.swift in Sources */, + B610F2EB27AA8E4500FCEBE9 /* ContentBlockingUpdatingTests.swift in Sources */, 4B0511E7262CAB3700F6079C /* UserDefaultsWrapperUtilities.swift in Sources */, 4BA1A6F6258C4F9600F6F690 /* EncryptionMocks.swift in Sources */, B6B3E0962654DACD0040E0A2 /* UTTypeTests.swift in Sources */, @@ -5362,8 +5438,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 8.0.2; + kind = upToNextMajorVersion; + minimumVersion = 10.0.0; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 2103a63075..4156907d34 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/duckduckgo/BrowserServicesKit", "state": { "branch": null, - "revision": "37cb952b7d6b13b4bd41261031c5fa03b421aae6", - "version": "8.0.2" + "revision": "b00ea4f240613766e303ccdcf7c5fed15f60ddd1", + "version": "10.0.0" } }, { diff --git a/DuckDuckGo/Autoconsent/AutoconsentBackground.swift b/DuckDuckGo/Autoconsent/AutoconsentBackground.swift new file mode 100644 index 0000000000..9745533cc1 --- /dev/null +++ b/DuckDuckGo/Autoconsent/AutoconsentBackground.swift @@ -0,0 +1,290 @@ +// +// AutoconsentBackground.swift +// +// Copyright © 2021 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 WebKit +import os +import BrowserServicesKit + +protocol AutoconsentManagement { + func clearCache() +} + +/// Central controller of autoconsent rules. Used by AutoconsentUserScript to query autoconsent rules +/// and coordinate their execution on tabs. +@available(macOS 11, *) +final class AutoconsentBackground: NSObject, WKScriptMessageHandlerWithReply, AutoconsentManagement { + + let tabMessageName = "browserTabsMessage" + let actionCallbackName = "actionResponse" + let readyMessageName = "ready" + + var injectionTime: WKUserScriptInjectionTime { .atDocumentStart } + var forMainFrameOnly: Bool { true } + let source: String = { + AutoconsentUserScript.loadJS("browser-shim", from: .main) + }() + + var tabs = [Int: TabFrameTracker]() + var messageCounter = 1 + var actionCallbacks = [Int: (Result) -> Void]() + private var ready = false + private var readyCallbacks: [() async -> Void] = [] + + let background: WKWebView + let decoder = JSONDecoder() + + var sitesNotifiedCache = Set() + + override init() { + let configuration = WKWebViewConfiguration() + background = WKWebView(frame: .zero, configuration: configuration) + super.init() + // configure background webview for two-way messaging. + configuration.userContentController.addUserScript(WKUserScript(source: source, + injectionTime: injectionTime, forMainFrameOnly: true, in: .page)) + configuration.userContentController.addScriptMessageHandler(self, contentWorld: .page, name: tabMessageName) + configuration.userContentController.addScriptMessageHandler(self, contentWorld: .page, name: actionCallbackName) + configuration.userContentController.addScriptMessageHandler(self, contentWorld: .page, name: readyMessageName) + let url = Bundle.main.url(forResource: "autoconsent", withExtension: "html")! + background.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent()) + } + + func ready(onReady: @escaping () async -> Void) { + if ready { + DispatchQueue.main.async { + Task { await onReady() } + } + + } else { + readyCallbacks.append(onReady) + } + } + + /// Runs an action on the autoconsent background page. This action can be one of: + /// - `detectCMP`: Check if there is a known CMP (Consent Management Platform) present on the page. + /// - `detectPopup`: If there is a CMP, check if they are showing the user a popup. + /// - `doOptOut`: Execute a series of clicks in the page to dismiss the popup and opt the user out of all configurable options. + /// - `selfTest`: If implemented for thie CMP, read back the consent state to check that the opt out was successful. + /// + /// The result of the action is provided in an async callback. + func callAction(in tabId: Int, action: Action, resultCallback: @escaping (Result) -> Void) { + // create a unique message ID so we can retrieve the callback when a response comes from the background page + let callbackId = messageCounter + messageCounter += 1 + self.actionCallbacks[callbackId] = resultCallback + background.evaluateJavaScript("window.callAction(\(callbackId), \(tabId), '\(action)')", in: nil, in: .page, completionHandler: { (result) in + switch result { + case .success: + break + case .failure(let error): + self.actionCallbacks[callbackId] = nil + resultCallback(.failure(error)) + } + }) + } + + /// Async version of callAction + @MainActor func callActionAsync(in tabId: Int, action: Action) async throws -> ActionResponse { + return try await withCheckedThrowingContinuation { continuation in + self.callAction(in: tabId, action: action, resultCallback: {result in + continuation.resume(with: result) + }) + } + } + + func detectCmp(in tabId: Int) async -> ActionResponse? { + do { + return try await callActionAsync(in: tabId, action: .detectCMP) + } catch { + return nil + } + } + + func isPopupOpen(in tabId: Int) async -> Bool { + do { + let response = try await callActionAsync(in: tabId, action: .detectPopup) + return response.result + } catch { + return false + } + } + + func doOptOut(in tabId: Int) async -> Bool { + do { + let response = try await callActionAsync(in: tabId, action: .doOptOut) + return response.result + } catch { + return false + } + } + + func testOptOutWorked(in tabId: Int) async throws -> ActionResponse { + return try await callActionAsync(in: tabId, action: .doOptOut) + } + + /// Process a message sent from the autoconsent userscript. + func onUserScriptMessage(in tabId: Int, _ message: WKScriptMessage) { + let webview = message.webView + let frame = message.frameInfo + var frameId = frame.hashValue + let ref = tabs[tabId] ?? TabFrameTracker() + + if frame.isMainFrame { + frameId = 0 + } + + ref.webview = webview + ref.frames[frameId] = frame + + // check for tabs which have been gced (i.e. the weak reference is now nil). These can be cleaned up both here and in the background page. + for (id, tab) in tabs where tab.webview == nil { + tabs[id] = nil + // delete entry in background script + background.evaluateJavaScript("window.autoconsent.removeTab(\(id));") + } + tabs[tabId] = ref + + let script = "_nativeMessageHandler(\(tabId), \(frameId), \(message.body));" + return background.evaluateJavaScript(script) + } + + func userContentController(_ userContentController: WKUserContentController, + didReceive message: WKScriptMessage, + replyHandler: @escaping (Any?, String?) -> Void) { + if message.name == tabMessageName { + // This is a message sent from the background to a specific tab and frame. We have to find the correct WKWebview and FrameInfo + // instances in order to push the message to the Userscript. + guard let jsonMessage = message.body as? String else { + replyHandler(false, "data decoding error") + return + } + forwardMessageToTab(message: jsonMessage, replyHandler: replyHandler) + } else if message.name == actionCallbackName { + // This is a message response to a call to #callAction. + guard let jsonMessage = message.body as? String, + let response = try? decoder.decode(ActionResponse.self, from: Data(jsonMessage.utf8)), + let callback = actionCallbacks[response.messageId] else { + replyHandler(nil, "Failed to parse message") + return + } + actionCallbacks[response.messageId] = nil + if response.error != nil { + os_log("Action error: %s", log: .autoconsent, type: .error, String(describing: response.error)) + callback(.failure(BackgroundError.actionError)) + } else { + callback(.success(response)) + } + replyHandler("OK", nil) + } else if message.name == readyMessageName { + ready = true + DispatchQueue.main.async { + self.readyCallbacks.forEach({ cb in Task { await cb() } }) + self.readyCallbacks.removeAll() + } + replyHandler("OK", nil) + } + } + + func forwardMessageToTab(message jsonMessage: String, replyHandler: @escaping (Any?, String?) -> Void) { + guard let payload = try? decoder.decode(BrowserTabMessage.self, from: Data(jsonMessage.utf8)) else { + replyHandler(false, "data decoding error") + return + } + let ref = tabs[payload.tabId] + guard let webview = ref?.webview, let frame = ref?.frames[payload.frameId] else { + replyHandler(false, "missing frame target") + return + } + var world: WKContentWorld = .defaultClient + var script = "window.autoconsent(\(jsonMessage))" + // Special case: for eval just run the script in page scope. + if payload.message.type == "eval" { + world = .page + script = """ +(() => { +try { + return !!(\(payload.message.script ?? "{}")) +} catch (e) {} +})(); +""" + } + + webview.evaluateJavaScript(script, in: frame, in: world, completionHandler: { (result) in + switch result { + case.failure(let error): + replyHandler(nil, "Error running \"\(script)\": \(error)") + case.success(let value): + replyHandler(value, nil) + } + }) + } + + func updateSettings(settings: [String: Any]?) { + let encoder = JSONEncoder() + guard let disabledCMPs = settings?["disabledCMPs"] as? [String], + let data = try? encoder.encode(disabledCMPs), + let cmpList = String(data: data, encoding: .utf8) else { + return + } + background.evaluateJavaScript("window.autoconsent.disableCMPs(\(cmpList));") + } + + func clearCache() { + sitesNotifiedCache.removeAll() + } + + final class TabFrameTracker { + weak var webview: WKWebView? + var frames = [Int: WKFrameInfo]() + } + + struct BrowserTabMessage: Codable { + var messageId: Int + var tabId: Int + var frameId: Int + var message: ContentScriptMessage + } + + struct ContentScriptMessage: Codable { + var type: String + var script: String? + var selectors: [String]? + } + + struct ActionResponse: Codable { + var messageId: Int + var ruleName: String? + var result: Bool + var error: String? + } + + enum BackgroundError: Error { + case invalidResponse + case actionError + } + + enum Action { + case detectCMP + case detectPopup + case doOptOut + case selfTest + case prehide + case unhide + } + +} diff --git a/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift new file mode 100644 index 0000000000..1a4ce48651 --- /dev/null +++ b/DuckDuckGo/Autoconsent/AutoconsentUserScript.swift @@ -0,0 +1,198 @@ +// +// AutoconsentUserScript.swift +// +// Copyright © 2021 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 WebKit +import os +import BrowserServicesKit + +protocol AutoconsentUserScriptDelegate: AnyObject { + func autoconsentUserScript(consentStatus: CookieConsentInfo) +} + +protocol UserScriptWithAutoconsent: UserScript { + var delegate: AutoconsentUserScriptDelegate? { get set } +} + +@available(macOS 11, *) +final class AutoconsentUserScript: NSObject, UserScriptWithAutoconsent { + + static var globalTabCounter = 0 + static var promptLastShown: Date? + static let background = AutoconsentBackground() + + var injectionTime: WKUserScriptInjectionTime { .atDocumentStart } + var forMainFrameOnly: Bool { false } + + enum Constants { + static let newSitePopupHidden = Notification.Name("newSitePopupHidden") + static let popupHiddenHostKey = "popupHiddenHostKey" + } + + private enum MessageName: String, CaseIterable { + case autoconsentBackgroundMessage + case autoconsentPageReady + } + public var messageNames: [String] { MessageName.allCases.map(\.rawValue) } + let source: String + let tabId: Int + let config: PrivacyConfiguration + var actionInProgress = false + var webview: WKWebView? + weak var delegate: AutoconsentUserScriptDelegate? + + init(scriptSource: ScriptSourceProviding, config: PrivacyConfiguration) { + source = Self.loadJS("autoconsent-bundle", from: .main, withReplacements: [:]) + Self.globalTabCounter += 1 + tabId = Self.globalTabCounter + self.config = config + } + + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { + guard let messageName = MessageName(rawValue: message.name) else { return } + if message.webView != nil { + webview = message.webView! + } + + switch messageName { + case .autoconsentBackgroundMessage: + // forward messages from Userscript to the background + return Self.background.onUserScriptMessage(in: tabId, message) + case .autoconsentPageReady: + // Page ready event (main frame): trigger CMP detection and opt-out if popup is being shown. + os_log("page ready: %s", log: .autoconsent, type: .debug, String(describing: message.body)) + guard let url = URL(string: message.body as? String ?? "") else { + return + } + onPageReady(url: url) + } + } + + func onPageReady(url: URL) { + let preferences = PrivacySecurityPreferences.shared + + guard preferences.autoconsentEnabled != false else { + os_log("autoconsent is disabled", log: .autoconsent, type: .debug) + return + } + + // reset dashboard state + self.delegate?.autoconsentUserScript(consentStatus: CookieConsentInfo( + consentManaged: Self.background.sitesNotifiedCache.contains(url.host ?? ""), optoutFailed: nil, selftestFailed: nil)) + + guard config.isFeature(.autoconsent, enabledForDomain: url.host) else { + os_log("disabled for site: %s", log: .autoconsent, type: .info, String(describing: url.absoluteString)) + return + } + + guard actionInProgress == false else { + return + } + + self.actionInProgress = true + + Self.background.ready { + // push current privacy config settings to the background page + Self.background.updateSettings(settings: self.config.settings(for: .autoconsent)) + let cmp = await Self.background.detectCmp(in: self.tabId) + guard cmp?.result == true else { + os_log("no CMP detected", log: .autoconsent, type: .info) + self.actionInProgress = false + return + } + os_log("popup found from %s", log: .autoconsent, type: .info, String(describing: cmp?.ruleName)) + // check if the user has explicitly enabled the feature + self.checkUserWasPrompted { enabled in + guard enabled else { + self.actionInProgress = false + return + } + Task { + await self.runOptOut(for: cmp!, on: url) + } + } + } + } + + func checkUserWasPrompted(callback: @escaping (Bool) -> Void) { + let preferences = PrivacySecurityPreferences.shared + guard preferences.autoconsentEnabled == nil else { + callback(true) + return + } + let now = Date.init() + guard Self.promptLastShown == nil || now > Self.promptLastShown!.addingTimeInterval(30) else { + callback(false) + return + } + Self.promptLastShown = now + let alert = NSAlert.cookiePopup() + alert.beginSheetModal(for: self.webview!.window!, completionHandler: { response in + switch response { + case .alertFirstButtonReturn: + // User wants to turn on the feature + preferences.autoconsentEnabled = true + callback(true) + case .alertSecondButtonReturn: + // "Not now" + callback(false) + case .alertThirdButtonReturn: + // "Don't ask again" + preferences.autoconsentEnabled = false + callback(false) + case _: + callback(false) + } + }) + } + + func runOptOut(for cmp: AutoconsentBackground.ActionResponse, on url: URL) async { + guard await Self.background.isPopupOpen(in: self.tabId) else { + os_log("popup not open", log: .autoconsent, type: .debug) + self.actionInProgress = false + return + } + + let optOutSuccessful = await Self.background.doOptOut(in: self.tabId) + guard optOutSuccessful else { + os_log("opt out failed: %s", log: .autoconsent, type: .error, String(describing: cmp.ruleName)) + self.delegate?.autoconsentUserScript(consentStatus: CookieConsentInfo( + consentManaged: true, optoutFailed: true, selftestFailed: nil)) + self.actionInProgress = false + return + } + os_log("opted out: %s", log: .autoconsent, type: .info, String(describing: cmp.ruleName)) + // post popover notification on main thread + DispatchQueue.main.async { + NotificationCenter.default.post(name: Constants.newSitePopupHidden, object: self, userInfo: [ + Constants.popupHiddenHostKey: url.host ?? "" + ]) + } + + do { + let response = try await Self.background.testOptOutWorked(in: self.tabId) + os_log("self test successful?: %s", log: .autoconsent, type: .debug, String(describing: response.result)) + self.delegate?.autoconsentUserScript(consentStatus: CookieConsentInfo( + consentManaged: true, optoutFailed: false, selftestFailed: false)) + } catch { + os_log("self test error: %s", log: .autoconsent, type: .error, error.localizedDescription) + self.delegate?.autoconsentUserScript(consentStatus: CookieConsentInfo( + consentManaged: true, optoutFailed: false, selftestFailed: true)) + } + self.actionInProgress = false + } +} diff --git a/DuckDuckGo/Autoconsent/autoconsent-bundle.js b/DuckDuckGo/Autoconsent/autoconsent-bundle.js new file mode 100644 index 0000000000..23a96f7ebb --- /dev/null +++ b/DuckDuckGo/Autoconsent/autoconsent-bundle.js @@ -0,0 +1,487 @@ +(function () { + 'use strict'; + + /** + * This code is in most parts copied from https://github.com/cavi-au/Consent-O-Matic/blob/master/Extension/Tools.js + * which is licened under the MIT. + */ + class Tools { + static setBase(base) { + Tools.base = base; + } + static findElement(options, parent = null, multiple = false) { + let possibleTargets = null; + if (parent != null) { + possibleTargets = Array.from(parent.querySelectorAll(options.selector)); + } + else { + if (Tools.base != null) { + possibleTargets = Array.from(Tools.base.querySelectorAll(options.selector)); + } + else { + possibleTargets = Array.from(document.querySelectorAll(options.selector)); + } + } + if (options.textFilter != null) { + possibleTargets = possibleTargets.filter(possibleTarget => { + let textContent = possibleTarget.textContent.toLowerCase(); + if (Array.isArray(options.textFilter)) { + let foundText = false; + for (let text of options.textFilter) { + if (textContent.indexOf(text.toLowerCase()) !== -1) { + foundText = true; + break; + } + } + return foundText; + } + else if (options.textFilter != null) { + return textContent.indexOf(options.textFilter.toLowerCase()) !== -1; + } + }); + } + if (options.styleFilters != null) { + possibleTargets = possibleTargets.filter(possibleTarget => { + let styles = window.getComputedStyle(possibleTarget); + let keep = true; + for (let styleFilter of options.styleFilters) { + let option = styles[styleFilter.option]; + if (styleFilter.negated) { + keep = keep && option !== styleFilter.value; + } + else { + keep = keep && option === styleFilter.value; + } + } + return keep; + }); + } + if (options.displayFilter != null) { + possibleTargets = possibleTargets.filter(possibleTarget => { + if (options.displayFilter) { + //We should be displayed + return possibleTarget.offsetHeight !== 0; + } + else { + //We should not be displayed + return possibleTarget.offsetHeight === 0; + } + }); + } + if (options.iframeFilter != null) { + possibleTargets = possibleTargets.filter(possibleTarget => { + if (options.iframeFilter) { + //We should be inside an iframe + return window.location !== window.parent.location; + } + else { + //We should not be inside an iframe + return window.location === window.parent.location; + } + }); + } + if (options.childFilter != null) { + possibleTargets = possibleTargets.filter(possibleTarget => { + let oldBase = Tools.base; + Tools.setBase(possibleTarget); + let childResults = Tools.find(options.childFilter); + Tools.setBase(oldBase); + return childResults.target != null; + }); + } + if (multiple) { + return possibleTargets; + } + else { + if (possibleTargets.length > 1) { + console.warn("Multiple possible targets: ", possibleTargets, options, parent); + } + return possibleTargets[0]; + } + } + static find(options, multiple = false) { + let results = []; + if (options.parent != null) { + let parent = Tools.findElement(options.parent, null, multiple); + if (parent != null) { + if (parent instanceof Array) { + parent.forEach(p => { + let targets = Tools.findElement(options.target, p, multiple); + if (targets instanceof Array) { + targets.forEach(target => { + results.push({ + parent: p, + target: target + }); + }); + } + else { + results.push({ + parent: p, + target: targets + }); + } + }); + return results; + } + else { + let targets = Tools.findElement(options.target, parent, multiple); + if (targets instanceof Array) { + targets.forEach(target => { + results.push({ + parent: parent, + target: target + }); + }); + } + else { + results.push({ + parent: parent, + target: targets + }); + } + } + } + } + else { + let targets = Tools.findElement(options.target, null, multiple); + if (targets instanceof Array) { + targets.forEach(target => { + results.push({ + parent: null, + target: target + }); + }); + } + else { + results.push({ + parent: null, + target: targets + }); + } + } + if (results.length === 0) { + results.push({ + parent: null, + target: null + }); + } + if (multiple) { + return results; + } + else { + if (results.length !== 1) { + console.warn("Multiple results found, even though multiple false", results); + } + return results[0]; + } + } + } + Tools.base = null; + + function matches(config) { + const result = Tools.find(config); + if (config.type === "css") { + return !!result.target; + } + else if (config.type === "checkbox") { + return !!result.target && result.target.checked; + } + } + async function executeAction(config, param) { + switch (config.type) { + case "click": + return clickAction(config); + case "list": + return listAction(config, param); + case "consent": + return consentAction(config, param); + case "ifcss": + return ifCssAction(config, param); + case "waitcss": + return waitCssAction(config); + case "foreach": + return forEachAction(config, param); + case "hide": + return hideAction(config); + case "slide": + return slideAction(config); + case "close": + return closeAction(); + case "wait": + return waitAction(config); + case "eval": + return evalAction(config); + default: + throw "Unknown action type: " + config.type; + } + } + const STEP_TIMEOUT = 0; + function waitTimeout(timeout) { + return new Promise(resolve => { + setTimeout(() => { + resolve(); + }, timeout); + }); + } + async function clickAction(config) { + const result = Tools.find(config); + if (result.target != null) { + result.target.click(); + } + return waitTimeout(STEP_TIMEOUT); + } + async function listAction(config, param) { + for (let action of config.actions) { + await executeAction(action, param); + } + } + async function consentAction(config, consentTypes) { + for (const consentConfig of config.consents) { + const shouldEnable = consentTypes.indexOf(consentConfig.type) !== -1; + if (consentConfig.matcher && consentConfig.toggleAction) { + const isEnabled = matches(consentConfig.matcher); + if (isEnabled !== shouldEnable) { + await executeAction(consentConfig.toggleAction); + } + } + else { + if (shouldEnable) { + await executeAction(consentConfig.trueAction); + } + else { + await executeAction(consentConfig.falseAction); + } + } + } + } + async function ifCssAction(config, param) { + const result = Tools.find(config); + if (!result.target) { + if (config.trueAction) { + await executeAction(config.trueAction, param); + } + } + else { + if (config.falseAction) { + await executeAction(config.falseAction, param); + } + } + } + async function waitCssAction(config) { + await new Promise(resolve => { + let numRetries = config.retries || 10; + const waitTime = config.waitTime || 250; + const checkCss = () => { + const result = Tools.find(config); + if ((config.negated && result.target) || + (!config.negated && !result.target)) { + if (numRetries > 0) { + numRetries -= 1; + setTimeout(checkCss, waitTime); + } + else { + resolve(); + } + } + else { + resolve(); + } + }; + checkCss(); + }); + } + async function forEachAction(config, param) { + const results = Tools.find(config, true); + const oldBase = Tools.base; + for (const result of results) { + if (result.target) { + Tools.setBase(result.target); + await executeAction(config.action, param); + } + } + Tools.setBase(oldBase); + } + async function hideAction(config) { + const result = Tools.find(config); + if (result.target) { + result.target.classList.add("Autoconsent-Hidden"); + // result.target.setAttribute("style", "display: none;"); + } + } + async function slideAction(config) { + const result = Tools.find(config); + const dragResult = Tools.find(config.dragTarget); + if (result.target) { + let targetBounds = result.target.getBoundingClientRect(); + let dragTargetBounds = dragResult.target.getBoundingClientRect(); + let yDiff = dragTargetBounds.top - targetBounds.top; + let xDiff = dragTargetBounds.left - targetBounds.left; + if (this.config.axis.toLowerCase() === "y") { + xDiff = 0; + } + if (this.config.axis.toLowerCase() === "x") { + yDiff = 0; + } + let screenX = window.screenX + targetBounds.left + targetBounds.width / 2.0; + let screenY = window.screenY + targetBounds.top + targetBounds.height / 2.0; + let clientX = targetBounds.left + targetBounds.width / 2.0; + let clientY = targetBounds.top + targetBounds.height / 2.0; + let mouseDown = document.createEvent("MouseEvents"); + mouseDown.initMouseEvent("mousedown", true, true, window, 0, screenX, screenY, clientX, clientY, false, false, false, false, 0, result.target); + let mouseMove = document.createEvent("MouseEvents"); + mouseMove.initMouseEvent("mousemove", true, true, window, 0, screenX + xDiff, screenY + yDiff, clientX + xDiff, clientY + yDiff, false, false, false, false, 0, result.target); + let mouseUp = document.createEvent("MouseEvents"); + mouseUp.initMouseEvent("mouseup", true, true, window, 0, screenX + xDiff, screenY + yDiff, clientX + xDiff, clientY + yDiff, false, false, false, false, 0, result.target); + result.target.dispatchEvent(mouseDown); + await this.waitTimeout(10); + result.target.dispatchEvent(mouseMove); + await this.waitTimeout(10); + result.target.dispatchEvent(mouseUp); + } + } + async function waitAction(config) { + await waitTimeout(config.waitTime); + } + async function closeAction(config) { + window.close(); + } + async function evalAction(config) { + console.log("eval!", config.code); + return new Promise(resolve => { + try { + if (config.async) { + window.eval(config.code); + setTimeout(() => { + resolve(window.eval("window.__consentCheckResult")); + }, config.timeout || 250); + } + else { + resolve(window.eval(config.code)); + } + } + catch (e) { + console.warn("eval error", e, config.code); + resolve(false); + } + }); + } + + let actionQueue = Promise.resolve(null); + const styleOverrideElementId = "autoconsent-css-rules"; + const styleSelector = `style#${styleOverrideElementId}`; + function handleMessage(message, debug = false) { + if (message.type === "click") { + const elem = document.querySelectorAll(message.selector); + debug && console.log("[click]", message.selector, elem); + if (elem.length > 0) { + if (message.all === true) { + elem.forEach(e => e.click()); + } + else { + elem[0].click(); + } + } + return elem.length > 0; + } + else if (message.type === "elemExists") { + const exists = document.querySelector(message.selector) !== null; + debug && console.log("[exists?]", message.selector, exists); + return exists; + } + else if (message.type === "elemVisible") { + const elem = document.querySelectorAll(message.selector); + const results = new Array(elem.length); + elem.forEach((e, i) => { + results[i] = e.offsetParent !== null || window.getComputedStyle(e).display !== "none" || e.style?.display !== "none"; + }); + if (results.length === 0) { + return false; + } + else if (message.check === "any") { + return results.some(r => r); + } + else if (message.check === "none") { + return results.every(r => !r); + } + // all + return results.every(r => r); + } + else if (message.type === "getAttribute") { + const elem = document.querySelector(message.selector); + if (!elem) { + return false; + } + return elem.getAttribute(message.attribute); + } + else if (message.type === "eval") { + // TODO: chrome support + const result = window.eval(message.script); // eslint-disable-line no-eval + debug && console.log("[eval]", message.script, result); + return result; + } + else if (message.type === "hide") { + const parent = document.head || + document.getElementsByTagName("head")[0] || + document.documentElement; + const rule = `${message.selectors.join(",")} { display: none !important; z-index: -1 !important; } `; + const existingElement = document.querySelector(styleSelector); + debug && console.log("[hide]", message.selectors, !!existingElement); + if (existingElement && existingElement instanceof HTMLStyleElement) { + existingElement.innerText += rule; + } + else { + const css = document.createElement("style"); + css.type = "text/css"; + css.id = styleOverrideElementId; + css.appendChild(document.createTextNode(rule)); + parent.appendChild(css); + } + return message.selectors.length > 0; + } + else if (message.type === "undohide") { + const existingElement = document.querySelector(styleSelector); + debug && console.log("[unhide]", !!existingElement); + if (existingElement) { + existingElement.remove(); + } + return !!existingElement; + } + else if (message.type === "matches") { + const matched = matches(message.config); + return matched; + } + else if (message.type === "executeAction") { + actionQueue = actionQueue.then(() => executeAction(message.config, message.param)); + return true; + } + return null; + } + + window.autoconsent = (payload) => { + return handleMessage(payload.message, false) + }; + + window.webkit.messageHandlers.autoconsentBackgroundMessage.postMessage(JSON.stringify({ + type: 'webNavigation.onCommitted', + url: window.location.href + })); + + const isMainDocument = window === window.top; + if (isMainDocument) { + setTimeout(() => { + window.webkit.messageHandlers.autoconsentPageReady.postMessage(window.location.href); + }, 100); + } + + window.onload = () => { + window.webkit.messageHandlers.autoconsentBackgroundMessage.postMessage(JSON.stringify({ + type: 'webNavigation.onCompleted', + url: window.location.href + })); + if (isMainDocument) { + window.webkit.messageHandlers.autoconsentPageReady.postMessage(window.location.href); + } + }; + +})(); diff --git a/DuckDuckGo/Autoconsent/autoconsent.html b/DuckDuckGo/Autoconsent/autoconsent.html new file mode 100644 index 0000000000..eca3bdcc46 --- /dev/null +++ b/DuckDuckGo/Autoconsent/autoconsent.html @@ -0,0 +1,26 @@ + + + + + + Autoconsent background + + + + + diff --git a/DuckDuckGo/Autoconsent/background-bundle.js b/DuckDuckGo/Autoconsent/background-bundle.js new file mode 100644 index 0000000000..9195e6f4a8 --- /dev/null +++ b/DuckDuckGo/Autoconsent/background-bundle.js @@ -0,0 +1,3977 @@ +(function () { + 'use strict'; + + /* eslint-disable no-restricted-syntax,no-await-in-loop,no-underscore-dangle */ + async function waitFor(predicate, maxTimes, interval) { + let result = await predicate(); + if (!result && maxTimes > 0) { + return new Promise((resolve) => { + setTimeout(async () => { + resolve(waitFor(predicate, maxTimes - 1, interval)); + }, interval); + }); + } + return Promise.resolve(result); + } + async function success(action) { + const result = await action; + if (!result) { + throw new Error(`Action failed: ${action}`); + } + return result; + } + class AutoConsentBase { + constructor(name) { + this.hasSelfTest = true; + this.name = name; + } + detectCmp(tab) { + throw new Error('Not Implemented'); + } + async detectPopup(tab) { + return false; + } + detectFrame(tab, frame) { + return false; + } + optOut(tab) { + throw new Error('Not Implemented'); + } + optIn(tab) { + throw new Error('Not Implemented'); + } + openCmp(tab) { + throw new Error('Not Implemented'); + } + async test(tab) { + // try IAB by default + return Promise.resolve(true); + } + } + async function evaluateRule(rule, tab) { + if (rule.frame && !tab.frame) { + await waitFor(() => Promise.resolve(!!tab.frame), 10, 500); + } + const frameId = rule.frame && tab.frame ? tab.frame.id : undefined; + const results = []; + if (rule.exists) { + results.push(tab.elementExists(rule.exists, frameId)); + } + if (rule.visible) { + results.push(tab.elementsAreVisible(rule.visible, rule.check, frameId)); + } + if (rule.eval) { + results.push(new Promise(async (resolve) => { + // catch eval error silently + try { + resolve(await tab.eval(rule.eval, frameId)); + } + catch (e) { + resolve(false); + } + })); + } + if (rule.waitFor) { + results.push(tab.waitForElement(rule.waitFor, rule.timeout || 10000, frameId)); + } + if (rule.click) { + if (rule.all === true) { + results.push(tab.clickElements(rule.click, frameId)); + } + else { + results.push(tab.clickElement(rule.click, frameId)); + } + } + if (rule.waitForThenClick) { + results.push(tab.waitForElement(rule.waitForThenClick, rule.timeout || 10000, frameId) + .then(() => tab.clickElement(rule.waitForThenClick, frameId))); + } + if (rule.wait) { + results.push(tab.wait(rule.wait)); + } + if (rule.goto) { + results.push(tab.goto(rule.goto)); + } + if (rule.hide) { + results.push(tab.hideElements(rule.hide, frameId)); + } + if (rule.undoHide) { + results.push(tab.undoHideElements(frameId)); + } + if (rule.waitForFrame) { + results.push(waitFor(() => !!tab.frame, 40, 500)); + } + // boolean and of results + return (await Promise.all(results)).reduce((a, b) => a && b, true); + } + class AutoConsent$1 extends AutoConsentBase { + constructor(config) { + super(config.name); + this.config = config; + } + get prehideSelectors() { + return this.config.prehideSelectors; + } + get isHidingRule() { + return this.config.isHidingRule; + } + async _runRulesParallel(tab, rules) { + const detections = await Promise.all(rules.map(rule => evaluateRule(rule, tab))); + return detections.every(r => !!r); + } + async _runRulesSequentially(tab, rules) { + for (const rule of rules) { + const result = await evaluateRule(rule, tab); + if (!result && !rule.optional) { + return false; + } + } + return true; + } + async detectCmp(tab) { + if (this.config.detectCmp) { + return this._runRulesParallel(tab, this.config.detectCmp); + } + return false; + } + async detectPopup(tab) { + if (this.config.detectPopup) { + return this._runRulesParallel(tab, this.config.detectPopup); + } + return false; + } + detectFrame(tab, frame) { + if (this.config.frame) { + return frame.url.startsWith(this.config.frame); + } + return false; + } + async optOut(tab) { + if (this.config.optOut) { + return this._runRulesSequentially(tab, this.config.optOut); + } + return false; + } + async optIn(tab) { + if (this.config.optIn) { + return this._runRulesSequentially(tab, this.config.optIn); + } + return false; + } + async openCmp(tab) { + if (this.config.openCmp) { + return this._runRulesSequentially(tab, this.config.openCmp); + } + return false; + } + async test(tab) { + if (this.config.test) { + return this._runRulesSequentially(tab, this.config.test); + } + return super.test(tab); + } + } + + class TabActions { + constructor(tabId, frame, sendContentMessage, browser) { + this.frame = frame; + this.sendContentMessage = sendContentMessage; + this.browser = browser; + this.id = tabId; + } + async elementExists(selector, frameId = 0) { + console.log(`check for ${selector} in tab ${this.id}, frame ${frameId}`); + return this.sendContentMessage(this.id, { + type: "elemExists", + selector + }, { + frameId + }); + } + async clickElement(selector, frameId = 0) { + console.log(`click element ${selector} in tab ${this.id}`); + return this.sendContentMessage(this.id, { + type: "click", + selector + }, { + frameId + }); + } + async clickElements(selector, frameId = 0) { + console.log(`click elements ${selector} in tab ${this.id}`); + return this.sendContentMessage(this.id, { + type: "click", + all: true, + selector + }, { + frameId + }); + } + async elementsAreVisible(selector, check, frameId = 0) { + return this.sendContentMessage(this.id, { + type: "elemVisible", + selector, + check + }, { + frameId + }); + } + async getAttribute(selector, attribute, frameId = 0) { + return this.sendContentMessage(this.id, { + type: "getAttribute", + selector, + attribute + }, { frameId }); + } + async eval(script, frameId = 0) { + // console.log(`run ${script} in tab ${this.id}`); + return await this.sendContentMessage(this.id, { + type: "eval", + script + }, { frameId }); + } + async waitForElement(selector, timeout, frameId = 0) { + const interval = 200; + const times = Math.ceil(timeout / interval); + return waitFor(() => this.elementExists(selector, frameId), times, interval); + } + async waitForThenClick(selector, timeout, frameId = 0) { + if (await this.waitForElement(selector, timeout, frameId)) { + return await this.clickElement(selector, frameId); + } + return false; + } + async hideElements(selectors, frameId = 0) { + return this.sendContentMessage(this.id, { + type: "hide", + selectors + }, { frameId }); + } + async undoHideElements(frameId = 0) { + return this.sendContentMessage(this.id, { + type: "undohide", + }, { frameId }); + } + async getBrowserTab() { + return this.browser.tabs.get(this.id); + } + async goto(url) { + return this.browser.tabs.update(this.id, { url }); + } + wait(ms) { + return new Promise(resolve => { + setTimeout(() => resolve(true), ms); + }); + } + matches(matcherConfig) { + return this.sendContentMessage(this.id, { + type: "matches", + config: matcherConfig + }, { frameId: 0 }); + } + executeAction(config, param) { + return this.sendContentMessage(this.id, { + type: "executeAction", + config, + param + }, { frameId: 0 }); + } + } + + Promise.resolve(null); + + class TabConsent { + constructor(tab, ruleCheckPromise) { + this.tab = tab; + this.optOutStatus = null; + this.checked = ruleCheckPromise; + ruleCheckPromise.then(rule => this.rule = rule); + } + getCMPName() { + if (this.rule) { + return this.rule.name; + } + return null; + } + async isPopupOpen(retries = 1, interval = 1000) { + const isOpen = await this.rule.detectPopup(this.tab); + if (!isOpen && retries > 0) { + return new Promise((resolve) => setTimeout(() => resolve(this.isPopupOpen(retries - 1, interval)), interval)); + } + return isOpen; + } + async doOptOut() { + try { + this.optOutStatus = await this.rule.optOut(this.tab); + return this.optOutStatus; + } + catch (e) { + this.optOutStatus = e; + throw e; + } + finally { + if (!this.rule.isHidingRule) { + if (this.getCMPName().startsWith('com_')) { + this.tab.wait(5000).then(() => this.tab.undoHideElements()); + } + else { + await this.tab.undoHideElements(); + } + } + } + } + async doOptIn() { + try { + return this.rule.optIn(this.tab); + } + finally { + if (!this.rule.isHidingRule) { + await this.tab.undoHideElements(); + } + } + } + hasTest() { + return !!this.rule.hasSelfTest; + } + async testOptOutWorked() { + return this.rule.test(this.tab); + } + async applyCosmetics(selectors) { + const hidden = await this.tab.hideElements(selectors); + return hidden; + } + } + + async function detectDialog(tab, retries, rules) { + let breakEarly = false; + const found = await new Promise(async (resolve) => { + let earlyReturn = false; + await Promise.all(rules.map(async (r, index) => { + try { + if (await r.detectCmp(tab)) { + earlyReturn = true; + resolve(index); + } + } + catch (e) { + breakEarly = true; + } + })); + if (!earlyReturn) { + resolve(-1); + } + }); + if (found === -1 && retries > 0 && !breakEarly) { + return new Promise((resolve) => { + setTimeout(async () => { + const result = detectDialog(tab, retries - 1, rules); + resolve(result); + }, 500); + }); + } + return found > -1 ? rules[found] : null; + } + + class TrustArc extends AutoConsentBase { + constructor() { + super("TrustArc"); + this.prehideSelectors = [ + ".trustarc-banner-container", + ".truste_popframe,.truste_overlay,.truste_box_overlay,#truste-consent-track", + ]; + } + detectFrame(_, frame) { + return frame.url.startsWith("https://consent-pref.trustarc.com/?"); + } + async detectCmp(tab) { + if (tab.frame && + tab.frame.url.startsWith("https://consent-pref.trustarc.com/?")) { + return true; + } + return tab.elementExists("#truste-show-consent"); + } + async detectPopup(tab) { + return ((await tab.elementsAreVisible("#truste-consent-content,#trustarc-banner-overlay")) || + (tab.frame && + (await tab.waitForElement("#defaultpreferencemanager", 5000, tab.frame.id)))); + } + async openFrame(tab) { + if (await tab.elementExists("#truste-show-consent")) { + await tab.clickElement("#truste-show-consent"); + } + } + async navigateToSettings(tab, frameId) { + // wait for it to load + await waitFor(async () => { + return ((await tab.elementExists(".shp", frameId)) || + (await tab.elementsAreVisible(".advance", "any", frameId)) || + tab.elementExists(".switch span:first-child", frameId)); + }, 10, 500); + // splash screen -> hit more information + if (await tab.elementExists(".shp", frameId)) { + await tab.clickElement(".shp", frameId); + } + await tab.waitForElement(".prefPanel", 5000, frameId); + // go to advanced settings if not yet shown + if (await tab.elementsAreVisible(".advance", "any", frameId)) { + await tab.clickElement(".advance", frameId); + } + // takes a while to load the opt-in/opt-out buttons + return await waitFor(() => tab.elementsAreVisible(".switch span:first-child", "any", frameId), 5, 1000); + } + async optOut(tab) { + // await tab.hideElements(['.truste_overlay', '.truste_box_overlay', '.trustarc-banner', '.truste-banner']); + if (await tab.elementExists("#truste-consent-required")) { + return tab.clickElement("#truste-consent-required"); + } + if (!tab.frame) { + await tab.clickElement("#truste-show-consent"); + await waitFor(async () => !!tab.frame && + (await tab.elementsAreVisible(".mainContent", "any", tab.frame.id)), 50, 100); + } + const frameId = tab.frame.id; + await waitFor(() => tab.eval("document.readyState === 'complete'", frameId), 20, 100); + tab.hideElements([".truste_popframe", ".truste_overlay", ".truste_box_overlay", "#truste-consent-track"]); + if (await tab.elementExists('.rejectAll', frameId)) { + return tab.clickElement('.rejectAll', frameId); + } + if (await tab.waitForElement('#catDetails0', 1000, frameId)) { + await tab.clickElement("#catDetails0", frameId); + return tab.clickElement(".submit", frameId); + } + if (await tab.elementExists(".required", frameId)) { + await tab.clickElement(".required", frameId); + } + else { + await this.navigateToSettings(tab, frameId); + await tab.clickElements(".switch span:nth-child(1):not(.active)", frameId); + await tab.clickElement(".submit", frameId); + } + try { + await tab.waitForThenClick("#gwt-debug-close_id", 20000, tab.frame.id); + } + catch (e) { + // ignore frame disappearing + } + return true; + } + async optIn(tab) { + if (!tab.frame) { + await this.openFrame(tab); + await waitFor(() => !!tab.frame, 10, 200); + } + const frameId = tab.frame.id; + await this.navigateToSettings(tab, frameId); + await tab.clickElements(".switch span:nth-child(2)", frameId); + await tab.clickElement(".submit", frameId); + await waitFor(() => tab.elementExists("#gwt-debug-close_id", frameId), 300, 1000); + await tab.clickElement("#gwt-debug-close_id", frameId); + return true; + } + async openCmp(tab) { + await tab.eval("truste.eu.clickListener()"); + return true; + } + async test() { + // TODO: find out how to test TrustArc + return true; + } + } + + class Cookiebot extends AutoConsentBase { + constructor() { + super('Cybotcookiebot'); + this.prehideSelectors = ["#CybotCookiebotDialog,#dtcookie-container,#cookiebanner"]; + } + async detectCmp(tab) { + try { + return await tab.eval('typeof window.CookieConsent === "object" && typeof window.CookieConsent.name === "string"'); + } + catch (e) { + return false; + } + } + detectPopup(tab) { + return tab.elementExists('#CybotCookiebotDialog,#dtcookie-container,#cookiebanner'); + } + async optOut(tab) { + if (await tab.elementExists('.cookie-alert-extended-detail-link')) { + await tab.clickElement('.cookie-alert-extended-detail-link'); + await tab.waitForElement('.cookie-alert-configuration', 1000); + await tab.clickElements('.cookie-alert-configuration-input:checked'); + return tab.clickElement('.cookie-alert-extended-button-secondary'); + } + if (await tab.elementExists('#dtcookie-container')) { + return tab.clickElement('.h-dtcookie-decline'); + } + if (await tab.elementExists('.cookiebot__button--settings')) { + await tab.clickElement('.cookiebot__button--settings'); + } + if (await tab.elementsAreVisible('#CybotCookiebotDialogBodyButtonDecline', 'all')) { + return await tab.clickElement('#CybotCookiebotDialogBodyButtonDecline'); + } + if (await tab.elementExists('.cookiebanner__link--details')) { + await tab.clickElement('.cookiebanner__link--details'); + } + await tab.clickElements('.CybotCookiebotDialogBodyLevelButton:checked:enabled,input[id*="CybotCookiebotDialogBodyLevelButton"]:checked:enabled'); + if (await tab.elementExists('#CybotCookiebotDialogBodyButtonDecline')) { + await tab.clickElement('#CybotCookiebotDialogBodyButtonDecline'); + } + if (await tab.elementExists('input[id^=CybotCookiebotDialogBodyLevelButton]:checked')) { + await tab.clickElements('input[id^=CybotCookiebotDialogBodyLevelButton]:checked'); + } + if (await tab.elementExists('#CybotCookiebotDialogBodyButtonAcceptSelected')) { + await tab.clickElement('#CybotCookiebotDialogBodyButtonAcceptSelected'); + } + else { + await tab.clickElements('#CybotCookiebotDialogBodyLevelButtonAccept,#CybotCookiebotDialogBodyButtonAccept,#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowallSelection'); + } + // some sites have custom submit buttons with no obvious selectors. In this case we just call the submitConsent API. + if (await tab.eval('CookieConsent.hasResponse !== true')) { + await tab.eval('Cookiebot.dialog.submitConsent() || true'); + await tab.wait(500); + } + return true; + } + async optIn(tab) { + if (await tab.elementExists('#dtcookie-container')) { + return tab.clickElement('.h-dtcookie-accept'); + } + await tab.clickElements('.CybotCookiebotDialogBodyLevelButton:not(:checked):enabled'); + await tab.clickElement('#CybotCookiebotDialogBodyLevelButtonAccept'); + await tab.clickElement('#CybotCookiebotDialogBodyButtonAccept'); + return true; + } + async openCmp(tab) { + await tab.eval('CookieConsent.renew() || true'); + return tab.waitForElement('#CybotCookiebotDialog', 10000); + } + async test(tab) { + return tab.eval('CookieConsent.declined === true'); + } + } + + class SourcePoint extends AutoConsentBase { + constructor() { + super("Sourcepoint"); + this.ccpaMode = false; + this.prehideSelectors = ["div[id^='sp_message_container_'],.message-overlay"]; + } + detectFrame(_, frame) { + try { + const url = new URL(frame.url); + if (url.searchParams.has('message_id') && url.hostname === 'ccpa-notice.sp-prod.net') { + this.ccpaMode = true; + return true; + } + return (url.pathname === '/index.html' || url.pathname === '/privacy-manager/index.html') + && url.searchParams.has('message_id') && url.searchParams.has('requestUUID'); + } + catch (e) { + return false; + } + } + async detectCmp(tab) { + return await tab.elementExists("div[id^='sp_message_container_']") || !!tab.frame; + } + async detectPopup(tab) { + return await tab.elementsAreVisible("div[id^='sp_message_container_']"); + } + async optIn(tab) { + return tab.clickElement(".sp_choice_type_11", tab.frame.id); + } + isManagerOpen(tab) { + return tab.frame && new URL(tab.frame.url).pathname === "/privacy-manager/index.html"; + } + async optOut(tab) { + try { + tab.hideElements(["div[id^='sp_message_container_']"]); + if (!this.isManagerOpen(tab)) { + if (!await waitFor(() => !!tab.frame, 30, 100)) { + throw "Frame never opened"; + } + if (!await tab.elementExists("button.sp_choice_type_12", tab.frame.id)) { + // do not sell button + return tab.clickElement('button.sp_choice_type_13', tab.frame.id); + } + await success(tab.clickElement("button.sp_choice_type_12", tab.frame.id)); + await waitFor(() => new URL(tab.frame.url).pathname === "/privacy-manager/index.html", 200, 100); + } + await tab.waitForElement('.type-modal', 20000, tab.frame.id); + // reject all button is offered by some sites + try { + const path = await Promise.race([ + tab.waitForElement('.sp_choice_type_REJECT_ALL', 2000, tab.frame.id).then(r => 0), + tab.waitForElement('.reject-toggle', 2000, tab.frame.id).then(() => 1), + tab.waitForElement('.pm-features', 2000, tab.frame.id).then(r => 2), + ]); + if (path === 0) { + await tab.wait(1000); + return await success(tab.clickElement('.sp_choice_type_REJECT_ALL', tab.frame.id)); + } + else if (path === 1) { + await tab.clickElement('.reject-toggle', tab.frame.id); + } + else { + await tab.waitForElement('.pm-features', 10000, tab.frame.id); + await tab.clickElements('.checked > span', tab.frame.id); + if (await tab.elementExists('.chevron', tab.frame.id)) { + await tab.clickElement('.chevron', tab.frame.id); + } + } + } + catch (e) { } + return await tab.clickElement('.sp_choice_type_SAVE_AND_EXIT', tab.frame.id); + } + finally { + tab.undoHideElements(); + } + } + async test(tab) { + await tab.eval("__tcfapi('getTCData', 2, r => window.__rcsResult = r)"); + return tab.eval("Object.values(window.__rcsResult.purpose.consents).every(c => !c)"); + } + } + + // Note: JS API is also available: + // https://help.consentmanager.net/books/cmp/page/javascript-api + class ConsentManager extends AutoConsentBase { + constructor() { + super("consentmanager.net"); + this.prehideSelectors = ["#cmpbox,#cmpbox2"]; + } + detectCmp(tab) { + return tab.elementExists("#cmpbox"); + } + detectPopup(tab) { + return tab.elementsAreVisible("#cmpbox .cmpmore", "any"); + } + async optOut(tab) { + if (await tab.elementExists(".cmpboxbtnno")) { + return tab.clickElement(".cmpboxbtnno"); + } + if (await tab.elementExists(".cmpwelcomeprpsbtn")) { + await tab.clickElements(".cmpwelcomeprpsbtn > a[aria-checked=true]"); + return await tab.clickElement(".cmpboxbtnsave"); + } + await tab.clickElement(".cmpboxbtncustom"); + await tab.waitForElement(".cmptblbox", 2000); + await tab.clickElements(".cmptdchoice > a[aria-checked=true]"); + return tab.clickElement(".cmpboxbtnyescustomchoices"); + } + async optIn(tab) { + return tab.clickElement(".cmpboxbtnyes"); + } + } + + // Note: JS API is also available: + // https://help.consentmanager.net/books/cmp/page/javascript-api + class Evidon extends AutoConsentBase { + constructor() { + super("Evidon"); + } + detectCmp(tab) { + return tab.elementExists("#_evidon_banner"); + } + detectPopup(tab) { + return tab.elementsAreVisible("#_evidon_banner"); + } + async optOut(tab) { + if (await tab.elementExists("#_evidon-decline-button")) { + return tab.clickElement("#_evidon-decline-button"); + } + tab.hideElements(["#evidon-prefdiag-overlay", "#evidon-prefdiag-background"]); + await tab.clickElement("#_evidon-option-button"); + await tab.waitForElement("#evidon-prefdiag-overlay", 5000); + return tab.clickElement("#evidon-prefdiag-decline"); + } + async optIn(tab) { + return tab.clickElement("#_evidon-accept-button"); + } + } + + const rules$3 = [ + new TrustArc(), + new Cookiebot(), + new SourcePoint(), + new ConsentManager(), + new Evidon(), + ]; + function createAutoCMP(config) { + return new AutoConsent$1(config); + } + + const rules$2 = rules$3; + + class ConsentOMaticCMP { + constructor(name, config) { + this.name = name; + this.config = config; + this.methods = new Map(); + config.methods.forEach(methodConfig => { + if (methodConfig.action) { + this.methods.set(methodConfig.name, methodConfig.action); + } + }); + this.hasSelfTest = this.methods.has("TEST_CONSENT"); + } + async detectCmp(tab) { + return (await Promise.all(this.config.detectors.map(detectorConfig => tab.matches(detectorConfig.presentMatcher)))).some(matched => matched); + } + async detectPopup(tab) { + return (await Promise.all(this.config.detectors.map(detectorConfig => tab.matches(detectorConfig.showingMatcher)))).some(matched => matched); + } + async executeAction(tab, method, param) { + if (this.methods.has(method)) { + return tab.executeAction(this.methods.get(method), param); + } + return true; + } + async optOut(tab) { + await this.executeAction(tab, "HIDE_CMP"); + await this.executeAction(tab, "OPEN_OPTIONS"); + await this.executeAction(tab, "HIDE_CMP"); + await this.executeAction(tab, "DO_CONSENT", []); + await this.executeAction(tab, "SAVE_CONSENT"); + return true; + } + async optIn(tab) { + await this.executeAction(tab, "HIDE_CMP"); + await this.executeAction(tab, "OPEN_OPTIONS"); + await this.executeAction(tab, "HIDE_CMP"); + await this.executeAction(tab, "DO_CONSENT", ['D', 'A', 'B', 'E', 'F', 'X']); + await this.executeAction(tab, "SAVE_CONSENT"); + return true; + } + async openCmp(tab) { + await this.executeAction(tab, "HIDE_CMP"); + await this.executeAction(tab, "OPEN_OPTIONS"); + return true; + } + test(tab) { + return this.executeAction(tab, "TEST_CONSENT"); + } + detectFrame(tab, frame) { + return false; + } + } + + // hide rules not specific to a single CMP rule + const globalHidden = [ + "#didomi-popup,.didomi-popup-container,.didomi-popup-notice,.didomi-consent-popup-preferences,#didomi-notice,.didomi-popup-backdrop,.didomi-screen-medium", + ]; + async function prehideElements(tab, rules) { + const selectors = rules.reduce((selectorList, rule) => { + if (rule.prehideSelectors) { + return [...selectorList, ...rule.prehideSelectors]; + } + return selectorList; + }, globalHidden); + await tab.hideElements(selectors); + } + + class AutoConsent { + constructor(browser, sendContentMessage) { + this.browser = browser; + this.sendContentMessage = sendContentMessage; + this.consentFrames = new Map(); + this.tabCmps = new Map(); + this.sendContentMessage = sendContentMessage; + this.rules = [...rules$2]; + } + addCMP(config) { + this.rules.push(createAutoCMP(config)); + } + disableCMPs(cmpNames) { + this.rules = this.rules.filter((cmp) => !cmpNames.includes(cmp.name)); + } + addConsentomaticCMP(name, config) { + this.rules.push(new ConsentOMaticCMP(`com_${name}`, config)); + } + createTab(tabId) { + return new TabActions(tabId, this.consentFrames.get(tabId), this.sendContentMessage, this.browser); + } + async checkTab(tabId, prehide = true) { + const tab = this.createTab(tabId); + if (prehide) { + this.prehideElements(tab); + } + const consent = new TabConsent(tab, this.detectDialog(tab, 20)); + this.tabCmps.set(tabId, consent); + // check tabs + consent.checked.then((rule) => { + if (this.consentFrames.has(tabId) && rule) { + const frame = this.consentFrames.get(tabId); + if (frame.type === rule.name) { + consent.tab.frame = frame; + } + } + // no CMP detected, undo hiding + if (!rule && prehide) { + tab.undoHideElements(); + } + }); + return this.tabCmps.get(tabId); + } + removeTab(tabId) { + this.tabCmps.delete(tabId); + this.consentFrames.delete(tabId); + } + onFrame({ tabId, url, frameId }) { + // ignore main frames + if (frameId === 0) { + return; + } + try { + const frame = { + id: frameId, + url: url, + }; + const tab = this.createTab(tabId); + const frameMatch = this.rules.findIndex(r => r.detectFrame(tab, frame)); + if (frameMatch > -1) { + this.consentFrames.set(tabId, { + type: this.rules[frameMatch].name, + url, + id: frameId, + }); + if (this.tabCmps.has(tabId)) { + this.tabCmps.get(tabId).tab.frame = this.consentFrames.get(tabId); + } + } + } + catch (e) { + console.error(e); + } + } + async detectDialog(tab, retries) { + return detectDialog(tab, retries, this.rules); + } + async prehideElements(tab) { + return prehideElements(tab, this.rules); + } + } + + var autoconsent = [ + { + name: "asus", + detectCmp: [ + { + exists: "#cookie-policy-info" + } + ], + detectPopup: [ + { + visible: "#cookie-policy-info" + } + ], + optIn: [ + { + click: ".btn-read-ck" + } + ], + optOut: [ + { + click: ".btn-setting" + }, + { + click: ".btn-save" + } + ] + }, + { + name: "cc_banner", + prehideSelectors: [ + ".cc_banner-wrapper" + ], + isHidingRule: true, + detectCmp: [ + { + exists: ".cc_banner-wrapper" + } + ], + detectPopup: [ + { + visible: ".cc_banner" + } + ], + optIn: [ + { + click: ".cc_btn_accept_all" + } + ], + optOut: [ + { + hide: [ + ".cc_banner-wrapper" + ] + } + ] + }, + { + name: "cookie-law-info", + prehideSelectors: [ + "#cookie-law-info-bar" + ], + detectCmp: [ + { + exists: "#cookie-law-info-bar" + } + ], + detectPopup: [ + { + visible: "#cookie-law-info-bar" + } + ], + optIn: [ + { + click: "[data-cli_action=\"accept\"]" + } + ], + optOut: [ + { + hide: [ + "#cookie-law-info-bar" + ] + }, + { + "eval": "CLI.disableAllCookies() || CLI.reject_close() || true" + } + ], + test: [ + { + "eval": "document.cookie.indexOf('cookielawinfo-checkbox-non-necessary=yes') === -1" + } + ] + }, + { + name: "cookie-notice", + prehideSelectors: [ + "#cookie-notice" + ], + isHidingRule: true, + detectCmp: [ + { + exists: "#cookie-notice" + } + ], + detectPopup: [ + { + visible: "#cookie-notice" + } + ], + optIn: [ + { + hide: [ + "#cn-accept-cookie" + ] + } + ], + optOut: [ + { + hide: [ + "#cookie-notice" + ] + } + ] + }, + { + name: "cookieconsent", + prehideSelectors: [ + "[aria-label=\"cookieconsent\"]" + ], + isHidingRule: true, + detectCmp: [ + { + exists: "[aria-label=\"cookieconsent\"]" + } + ], + detectPopup: [ + { + visible: "[aria-label=\"cookieconsent\"]" + } + ], + optIn: [ + { + click: ".cc-dismiss" + } + ], + optOut: [ + { + hide: [ + "[aria-label=\"cookieconsent\"]" + ] + } + ] + }, + { + name: "Drupal", + detectCmp: [ + { + exists: "#drupalorg-crosssite-gdpr" + } + ], + detectPopup: [ + { + visible: "#drupalorg-crosssite-gdpr" + } + ], + optOut: [ + { + click: ".no" + } + ], + optIn: [ + { + click: ".yes" + } + ] + }, + { + name: "eu-cookie-compliance-banner", + isHidingRule: true, + detectCmp: [ + { + exists: ".eu-cookie-compliance-banner-info" + } + ], + detectPopup: [ + { + visible: ".eu-cookie-compliance-banner-info" + } + ], + optIn: [ + { + click: ".agree-button" + } + ], + optOut: [ + { + click: ".decline-button,.eu-cookie-compliance-save-preferences-button", + optional: true + }, + { + hide: [ + ".eu-cookie-compliance-banner-info", + "#sliding-popup" + ] + } + ], + test: [ + { + "eval": "document.cookie.indexOf('cookie-agreed=2') === -1" + } + ] + }, + { + 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: true, + optional: true + }, + { + click: ".fc-confirm-choices", + optional: true + } + ], + optIn: [ + { + click: ".fc-cta-consent" + } + ] + }, + { + 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: "klaro", + detectCmp: [ + { + exists: ".klaro > .cookie-notice" + } + ], + detectPopup: [ + { + visible: ".klaro > .cookie-notice" + } + ], + optIn: [ + { + click: ".cm-btn-success" + } + ], + optOut: [ + { + click: ".cn-decline" + } + ], + test: [ + { + "eval": "Object.values(klaro.getManager().consents).every(c => !c)" + } + ] + }, + { + name: "notice-cookie", + prehideSelectors: [ + ".button--notice" + ], + isHidingRule: true, + detectCmp: [ + { + exists: ".notice--cookie" + } + ], + detectPopup: [ + { + visible: ".notice--cookie" + } + ], + optIn: [ + { + click: ".button--notice" + } + ], + optOut: [ + { + hide: [ + ".notice--cookie" + ] + } + ] + }, + { + name: "Onetrust", + prehideSelectors: [ + "#onetrust-banner-sdk,#onetrust-consent-sdk,.optanon-alert-box-wrapper,.onetrust-pc-dark-filter,.js-consent-banner" + ], + isHidingRule: true, + detectCmp: [ + { + exists: "#onetrust-banner-sdk,.optanon-alert-box-wrapper" + } + ], + detectPopup: [ + { + visible: "#onetrust-banner-sdk,.optanon-alert-box-wrapper" + } + ], + optOut: [ + { + click: "#onetrust-pc-btn-handler,.ot-sdk-show-settings,button.js-cookie-settings" + }, + { + waitFor: "#onetrust-consent-sdk", + timeout: 2000 + }, + { + wait: 1000 + }, + { + click: "#onetrust-consent-sdk input.category-switch-handler:checked,.js-editor-toggle-state:checked", + all: true, + optional: true + }, + { + waitForThenClick: ".save-preference-btn-handler,.js-consent-save", + timeout: 1000 + } + ], + optIn: [ + { + click: "onetrust-accept-btn-handler,js-accept-cookies" + } + ], + test: [ + { + "eval": "window.OnetrustActiveGroups.split(',').filter(s => s.length > 0).length <= 1" + } + ] + }, + { + name: "osano", + prehideSelectors: [ + ".osano-cm-window" + ], + isHidingRule: true, + detectCmp: [ + { + exists: ".osano-cm-window" + } + ], + detectPopup: [ + { + visible: ".osano-cm-dialog" + } + ], + optIn: [ + { + click: ".osano-cm-accept-all" + } + ], + optOut: [ + { + hide: [ + ".osano-cm-window" + ] + } + ] + }, + { + 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: true, + optional: true + }, + { + click: ".qc-cmp2-main button[aria-label=\"REJECT ALL\"]", + optional: true + }, + { + waitForThenClick: ".qc-cmp2-main button[aria-label=\"SAVE & EXIT\"],.qc-cmp2-buttons-desktop > button[mode=\"primary\"]", + timeout: 5000 + } + ], + optIn: [ + { + click: ".qc-cmp2-summary-buttons > button[mode=\"primary\"]" + } + ] + }, + { + name: "Tealium", + prehideSelectors: [ + "#__tealiumGDPRecModal,#__tealiumGDPRcpPrefs,#consent-layer" + ], + isHidingRule: false, + detectCmp: [ + { + exists: "#__tealiumGDPRecModal" + }, + { + "eval": "window.utag && typeof utag.gdpr === 'object'" + } + ], + detectPopup: [ + { + visible: "#__tealiumGDPRecModal" + } + ], + optOut: [ + { + hide: [ + "#__tealiumGDPRecModal", + "#__tealiumGDPRcpPrefs", + "#consent-layer" + ] + }, + { + click: "#cm-acceptNone,.js-accept-essential-cookies" + } + ], + optIn: [ + { + hide: [ + "#__tealiumGDPRecModal" + ] + }, + { + "eval": "utag.gdpr.setConsentValue(true)" + } + ], + test: [ + { + "eval": "utag.gdpr.getConsentState() !== 1" + } + ] + }, + { + name: "Test page CMP", + prehideSelectors: [ + "#reject-all" + ], + detectCmp: [ + { + exists: "#privacy-test-page-cmp-test" + } + ], + detectPopup: [ + { + visible: "#privacy-test-page-cmp-test" + } + ], + optIn: [ + { + click: "#accept-all" + } + ], + optOut: [ + { + waitFor: "#reject-all" + }, + { + click: "#reject-all" + } + ], + test: [ + { + "eval": "window.results.results[0] === 'button_clicked'" + } + ] + } + ]; + var consentomatic = { + "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: true, + 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: true, + 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: true, + 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" + } + ] + } + }; + var rules = { + autoconsent: autoconsent, + consentomatic: consentomatic + }; + + var rules$1 = /*#__PURE__*/Object.freeze({ + __proto__: null, + autoconsent: autoconsent, + consentomatic: consentomatic, + 'default': rules + }); + + /* global browser */ + + const consent = new AutoConsent(browser, browser.tabs.sendMessage); + + async function loadRules () { + console.log(rules$1); + Object.keys(consentomatic).forEach((name) => { + consent.addConsentomaticCMP(name, consentomatic[name]); + }); + autoconsent.forEach((rule) => { + consent.addCMP(rule); + }); + } + + loadRules(); + + browser.webNavigation.onCommitted.addListener((details) => { + if (details.frameId === 0) { + consent.removeTab(details.tabId); + } + }, { + url: [{ schemes: ['http', 'https'] }] + }); + + browser.webNavigation.onCompleted.addListener(consent.onFrame.bind(consent), { + url: [{ schemes: ['http', 'https'] }] + }); + + window.autoconsent = consent; + + window.callAction = (messageId, tabId, action) => { + const respond = (obj) => { + window.webkit.messageHandlers.actionResponse.postMessage(JSON.stringify({ + messageId, + ...obj + })).catch(() => console.warn('Error sending response', messageId, obj)); + }; + const errorResponse = (err) => { + console.warn('action error', err); + respond({ result: false, error: err.toString() }); + }; + + if (action === 'detectCMP') { + consent.checkTab(tabId).then(async (cmp) => { + try { + await cmp.checked; + respond({ + ruleName: cmp.getCMPName(), + result: cmp.getCMPName() !== null + }); + } catch (e) { + errorResponse(e); + } + }, errorResponse); + } else { + const cmp = consent.tabCmps.get(tabId); + if (!cmp) { + respond({ + result: false + }); + return + } + const successResponse = (result) => respond({ ruleName: cmp.getCMPName(), result }); + switch (action) { + case 'detectPopup': + cmp.isPopupOpen(20, 100).then(successResponse, errorResponse); + break + case 'doOptOut': + cmp.doOptOut().then(successResponse, errorResponse); + break + case 'selfTest': + if (!cmp.hasTest()) { + errorResponse('no test for this CMP'); + } else { + cmp.testOptOutWorked().then(successResponse, errorResponse); + } + break + } + } + return messageId + }; + +})(); diff --git a/DuckDuckGo/Autoconsent/background.js b/DuckDuckGo/Autoconsent/background.js new file mode 100644 index 0000000000..44aacff414 --- /dev/null +++ b/DuckDuckGo/Autoconsent/background.js @@ -0,0 +1,83 @@ +/* global browser */ +import AutoConsent from '@cliqz/autoconsent/lib/web' +import * as rules from '@cliqz/autoconsent/rules/rules.json' + +const consent = new AutoConsent(browser, browser.tabs.sendMessage) + +async function loadRules () { + console.log(rules) + Object.keys(rules.consentomatic).forEach((name) => { + consent.addConsentomaticCMP(name, rules.consentomatic[name]) + }) + rules.autoconsent.forEach((rule) => { + consent.addCMP(rule) + }) +} + +loadRules() + +browser.webNavigation.onCommitted.addListener((details) => { + if (details.frameId === 0) { + consent.removeTab(details.tabId) + } +}, { + url: [{ schemes: ['http', 'https'] }] +}) + +browser.webNavigation.onCompleted.addListener(consent.onFrame.bind(consent), { + url: [{ schemes: ['http', 'https'] }] +}) + +window.autoconsent = consent + +window.callAction = (messageId, tabId, action) => { + const respond = (obj) => { + window.webkit.messageHandlers.actionResponse.postMessage(JSON.stringify({ + messageId, + ...obj + })).catch(() => console.warn('Error sending response', messageId, obj)) + } + const errorResponse = (err) => { + console.warn('action error', err) + respond({ result: false, error: err.toString() }) + } + + if (action === 'detectCMP') { + consent.checkTab(tabId).then(async (cmp) => { + try { + await cmp.checked + respond({ + ruleName: cmp.getCMPName(), + result: cmp.getCMPName() !== null + }) + } catch (e) { + errorResponse(e) + } + }, errorResponse) + } else { + const cmp = consent.tabCmps.get(tabId) + if (!cmp) { + respond({ + result: false + }) + return + } + const successResponse = (result) => respond({ ruleName: cmp.getCMPName(), result }) + switch (action) { + case 'detectPopup': + cmp.isPopupOpen(20, 100).then(successResponse, errorResponse) + break + case 'doOptOut': + cmp.doOptOut().then(successResponse, errorResponse) + break + case 'selfTest': + if (!cmp.hasTest()) { + errorResponse('no test for this CMP') + } else { + cmp.testOptOutWorked().then(successResponse, errorResponse) + } + break + } + } + return messageId +} diff --git a/DuckDuckGo/Autoconsent/browser-shim.js b/DuckDuckGo/Autoconsent/browser-shim.js new file mode 100644 index 0000000000..f0e146645e --- /dev/null +++ b/DuckDuckGo/Autoconsent/browser-shim.js @@ -0,0 +1,77 @@ +let _msgCtr = 0 +const tabStore = new Map() + +class WebNavigationListener { + constructor () { + this._listeners = [] + } + + _trigger (args) { + this._listeners.forEach(({ fn }) => fn(args)) + } + + addListener (fn, filter) { + this._listeners.push({ fn, filter }) + } +} + +window.browser = { + webNavigation: { + onCommitted: new WebNavigationListener(), + onCompleted: new WebNavigationListener() + }, + tabs: { + get (tabId) { + return Promise.resolve(tabStore.get(tabId)) + }, + sendMessage: (tabId, message, { frameId } = { frameId: 0 }) => { + const messageId = _msgCtr++ + return window.webkit.messageHandlers.browserTabsMessage.postMessage(JSON.stringify({ + messageId, + tabId, + message, + frameId + })) + } + }, + runtime: { + onMessage: { + _listeners: [], + _trigger (...args) { + window.browser.runtime.onMessage._listeners.forEach((fn) => fn(...args)) + }, + addListener (cb) { + window.browser.runtime.onMessage._listeners.push(cb) + } + } + } +} + +window._nativeMessageHandler = (tabId, frameId, message) => { + // console.log(tabId, frameId, message) + switch (message.type) { + case 'webNavigation.onCommitted': + return window.browser.webNavigation.onCommitted._trigger({ + tabId, + frameId, + url: message.url, + timeStamp: Date.now() + }) + case 'webNavigation.onCompleted': + return window.browser.webNavigation.onCompleted._trigger({ + tabId, + frameId, + url: message.url, + timeStamp: Date.now() + }) + case 'runtime.sendMessage': + return window.browser.runtime.onMessage._trigger(message.payload, { + tab: { + id: tabId + }, + frameId + }) + } +} + +setTimeout(() => window.webkit.messageHandlers.ready.postMessage({}), 50) diff --git a/DuckDuckGo/Autoconsent/userscript.js b/DuckDuckGo/Autoconsent/userscript.js new file mode 100644 index 0000000000..38749bc1ca --- /dev/null +++ b/DuckDuckGo/Autoconsent/userscript.js @@ -0,0 +1,27 @@ +import handleContentMessage from '@cliqz/autoconsent/lib/web/content' + +window.autoconsent = (payload) => { + return handleContentMessage(payload.message, false) +} + +window.webkit.messageHandlers.autoconsentBackgroundMessage.postMessage(JSON.stringify({ + type: 'webNavigation.onCommitted', + url: window.location.href +})) + +const isMainDocument = window === window.top +if (isMainDocument) { + setTimeout(() => { + window.webkit.messageHandlers.autoconsentPageReady.postMessage(window.location.href) + }, 100) +} + +window.onload = () => { + window.webkit.messageHandlers.autoconsentBackgroundMessage.postMessage(JSON.stringify({ + type: 'webNavigation.onCompleted', + url: window.location.href + })) + if (isMainDocument) { + window.webkit.messageHandlers.autoconsentPageReady.postMessage(window.location.href) + } +} diff --git a/DuckDuckGo/BrowserTab/Model/Tab.swift b/DuckDuckGo/BrowserTab/Model/Tab.swift index 39de55b8d7..54ffcf2af0 100644 --- a/DuckDuckGo/BrowserTab/Model/Tab.swift +++ b/DuckDuckGo/BrowserTab/Model/Tab.swift @@ -99,14 +99,14 @@ final class Tab: NSObject { } weak var delegate: TabDelegate? + private let cbaTimeReporter: ContentBlockingAssetsCompilationTimeReporter? init(content: TabContent, faviconManagement: FaviconManagement = FaviconManager.shared, webCacheManager: WebCacheManager = WebCacheManager.shared, - webViewConfiguration: WebViewConfiguration? = nil, + webViewConfiguration: WKWebViewConfiguration? = nil, historyCoordinating: HistoryCoordinating = HistoryCoordinator.shared, - scriptsSource: ScriptSourceProviding = DefaultScriptSourceProvider.shared, - contentBlockingManager: ContentBlockerRulesManager = ContentBlocking.contentBlockingManager, + cbaTimeReporter: ContentBlockingAssetsCompilationTimeReporter? = ContentBlockingAssetsCompilationTimeReporter.shared, visitedDomains: Set = Set(), title: String? = nil, error: Error? = nil, @@ -119,8 +119,7 @@ final class Tab: NSObject { self.content = content self.faviconManagement = faviconManagement self.historyCoordinating = historyCoordinating - self.scriptsSource = scriptsSource - self.contentBlockingManager = contentBlockingManager + self.cbaTimeReporter = cbaTimeReporter self.visitedDomains = visitedDomains self.title = title self.error = error @@ -141,7 +140,15 @@ final class Tab: NSObject { } deinit { - userScripts?.remove(from: webView.configuration.userContentController) + self.userContentController.removeAllUserScripts() + +#if DEBUG + assert(self.isClosing || !content.isUrl, "tabWillClose() was not called for this Tab") +#endif + } + + private var userContentController: UserContentController { + (webView.configuration.userContentController as? UserContentController)! } // MARK: - Event Publishers @@ -316,25 +323,25 @@ final class Tab: NSObject { @discardableResult private func setFBProtection(enabled: Bool) -> Bool { - guard self.fbBlockingEnabled != enabled else { - return false - } + guard self.fbBlockingEnabled != enabled else { return false } - if let fbRules = contentBlockingManager.currentRules.first(where: { - $0.name == ContentBlockerRulesLists.Constants.clickToLoadRulesListName - }) { - if self.fbBlockingEnabled { - self.fbBlockingEnabled = false - webView.configuration.userContentController.remove(fbRules.rulesList) - } else { - self.fbBlockingEnabled = true - webView.configuration.userContentController.add(fbRules.rulesList) + if enabled { + do { + try userContentController.enableContentRuleList(withIdentifier: ContentBlockerRulesLists.Constants.clickToLoadRulesListName) + } catch { + assertionFailure("Missing FB List") + return false } - return true } else { - assertionFailure("Missing FB List") + userContentController.disableContentRuleList(withIdentifier: ContentBlockerRulesLists.Constants.clickToLoadRulesListName) } - return false + self.fbBlockingEnabled = enabled + + return true + } + + var cbrCompletionTokensPublisher: AnyPublisher<[ContentBlockerRulesManager.CompletionToken], Never> { + userContentController.$contentBlockingAssets.compactMap { $0?.completionTokens }.eraseToAnyPublisher() } private func reloadIfNeeded(shouldLoadInBackground: Bool = false) { @@ -385,9 +392,8 @@ final class Tab: NSObject { webView.navigationDelegate = self webView.allowsBackForwardNavigationGestures = true webView.allowsMagnification = true + userContentController.delegate = self - userScripts = UserScripts(with: scriptsSource) - subscribeToUserScriptChanges() subscribeToOpenExternalUrlEvents() superviewObserver = webView.observe(\.superview, options: .old) { [weak self] _, change in @@ -401,6 +407,20 @@ final class Tab: NSObject { reloadIfNeeded(shouldLoadInBackground: shouldLoadInBackground) } +#if DEBUG + private var isClosing = false +#endif + + func tabWillClose() { + webView.stopLoading() + webView.stopMediaCapture() + cbaTimeReporter?.tabWillClose(self) + +#if DEBUG + self.isClosing = true +#endif + } + // MARK: - Open External URL let externalUrlHandler = ExternalURLHandler() @@ -438,11 +458,6 @@ final class Tab: NSObject { // MARK: - User Scripts - let scriptsSource: ScriptSourceProviding - private var userScriptsUpdatedCancellable: AnyCancellable? - - let contentBlockingManager: ContentBlockerRulesManager - lazy var emailManager: EmailManager = { let emailManager = EmailManager() emailManager.requestDelegate = self @@ -455,41 +470,9 @@ final class Tab: NSObject { return manager }() - private var userScripts: UserScripts! { - willSet { - if let userScripts = userScripts { - userScripts.remove(from: webView.configuration.userContentController) - } - } - didSet { - userScripts.debugScript.instrumentation = instrumentation - userScripts.faviconScript.delegate = self - userScripts.contextMenuScript.delegate = self - userScripts.surrogatesScript.delegate = self - userScripts.contentBlockerRulesScript.delegate = self - userScripts.clickToLoadScript.delegate = self - userScripts.autofillScript.emailDelegate = emailManager - userScripts.autofillScript.vaultDelegate = vaultManager - userScripts.pageObserverScript.delegate = self - userScripts.printingUserScript.delegate = self - userScripts.hoverUserScript.delegate = self - - attachFindInPage() - - userScripts.install(into: webView.configuration.userContentController) - } - } - - private func subscribeToUserScriptChanges() { - userScriptsUpdatedCancellable = scriptsSource.sourceUpdatedPublisher.receive(on: RunLoop.main).sink { [weak self] _ in - guard let self = self, self.delegate != nil else { return } - - self.userScripts = UserScripts(with: self.scriptsSource) - } - } - // MARK: - Find in Page + var findInPageScript: FindInPageUserScript? var findInPageCancellable: AnyCancellable? private func subscribeToFindInPageTextChange() { findInPageCancellable?.cancel() @@ -501,7 +484,7 @@ final class Tab: NSObject { } private func attachFindInPage() { - userScripts.findInPageScript.model = findInPage + findInPageScript?.model = findInPage subscribeToFindInPageTextChange() } @@ -535,6 +518,7 @@ final class Tab: NSObject { @Published var trackerInfo: TrackerInfo? @Published var serverTrust: ServerTrust? @Published var connectionUpgradedTo: URL? + @Published var cookieConsentManaged: CookieConsentInfo? public func resetDashboardInfo(_ url: URL?) { trackerInfo = TrackerInfo() @@ -567,6 +551,27 @@ final class Tab: NSObject { } +extension Tab: UserContentControllerDelegate { + + func userContentController(_ userContentController: UserContentController, didInstallUserScripts userScripts: UserScripts) { + userScripts.debugScript.instrumentation = instrumentation + userScripts.faviconScript.delegate = self + userScripts.contextMenuScript.delegate = self + userScripts.surrogatesScript.delegate = self + userScripts.contentBlockerRulesScript.delegate = self + userScripts.clickToLoadScript.delegate = self + userScripts.autofillScript.emailDelegate = emailManager + userScripts.autofillScript.vaultDelegate = vaultManager + userScripts.pageObserverScript.delegate = self + userScripts.printingUserScript.delegate = self + userScripts.hoverUserScript.delegate = self + userScripts.autoconsentUserScript?.delegate = self + + attachFindInPage() + } + +} + extension Tab: PrintingUserScriptDelegate { func printingUserScriptDidRequestPrintController(_ script: PrintingUserScript) { @@ -652,7 +657,7 @@ extension Tab: ClickToLoadUserScriptDelegate { replyHandler(true) return } - + if setFBProtection(enabled: false) { replyHandler(true) } else { @@ -733,9 +738,9 @@ extension Tab: WKNavigationDelegate { // swiftlint:disable cyclomatic_complexity // swiftlint:disable function_body_length + @MainActor func webView(_ webView: WKWebView, - decidePolicyFor navigationAction: WKNavigationAction, - decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { + decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy { webView.customUserAgent = UserAgent.for(navigationAction.request.url) @@ -746,15 +751,16 @@ extension Tab: WKNavigationDelegate { // Auto-cancel simulated Back action when upgrading to HTTPS or GPC from Client Redirect self.webView.frozenCanGoForward = nil self.webView.frozenCanGoBack = nil - decisionHandler(.cancel) - return + + return .cancel } else if navigationAction.navigationType != .backForward, let request = GPCRequestFactory.shared.requestForGPC(basedOn: navigationAction.request) { self.invalidateBackItemIfNeeded(for: navigationAction) - decisionHandler(.cancel) - webView.load(request) - return + defer { + webView.load(request) + } + return .cancel } } @@ -770,35 +776,23 @@ extension Tab: WKNavigationDelegate { let isLinkActivated = navigationAction.navigationType == .linkActivated let isMiddleClicked = navigationAction.buttonNumber == Constants.webkitMiddleClick if isLinkActivated && NSApp.isCommandPressed || isMiddleClicked { - decisionHandler(.cancel) - delegate?.tab(self, requestedNewTabWith: navigationAction.request.url.map { .url($0) } ?? .none, selected: NSApp.isShiftPressed) - return + defer { + delegate?.tab(self, requestedNewTabWith: navigationAction.request.url.map { .url($0) } ?? .none, selected: NSApp.isShiftPressed) + } + return .cancel } else if isLinkActivated && NSApp.isOptionPressed && !NSApp.isCommandPressed { - decisionHandler(.download(navigationAction, using: webView)) - return + return .download(navigationAction, using: webView) } guard let url = navigationAction.request.url, let urlScheme = url.scheme else { self.willPerformNavigationAction(navigationAction) - decisionHandler(.allow) - return - } - - let privacyConfigurationManager = ContentBlocking.privacyConfigurationManager - let privacyConfiguration = privacyConfigurationManager.privacyConfig - - let featureEnabled = privacyConfiguration.isFeature(.clickToPlay, enabledForDomain: url.host) - if featureEnabled { - setFBProtection(enabled: true) - } else { - setFBProtection(enabled: false) + return .allow } if navigationAction.shouldDownload { // register the navigationAction for legacy _WKDownload to be called back on the Tab // further download will be passed to webView:navigationAction:didBecomeDownload: - decisionHandler(.download(navigationAction, using: webView)) - return + return .download(navigationAction, using: webView) } else if externalUrlHandler.isExternal(scheme: urlScheme) { // ignore