diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index d1f94e095a..49bb2a2a95 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -77,7 +77,6 @@ 1DFAB5232A8983E100A0F7F6 /* SetExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DFAB51F2A89830D00A0F7F6 /* SetExtensionTests.swift */; }; 1E0C72062ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */; }; 1E0C72072ABC63BD00802009 /* SubscriptionPagesUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E0C72052ABC63BD00802009 /* SubscriptionPagesUserScript.swift */; }; - 1E25269C28F8741A00E44DFA /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 1E25269B28F8741A00E44DFA /* Common */; }; 1E2AE4C72ACB215900684E0A /* NetworkProtectionRemoteMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15D62ABB8A110083F6DF /* NetworkProtectionRemoteMessaging.swift */; }; 1E2AE4C82ACB216B00684E0A /* HoverTrackingArea.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6B140872ABDBCC1004F8E85 /* HoverTrackingArea.swift */; }; 1E2AE4C92ACB217800684E0A /* HomePageRemoteMessagingStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF15DA2ABB8CED0083F6DF /* HomePageRemoteMessagingStorage.swift */; }; @@ -632,7 +631,6 @@ 3706FCAB293F65D500E42796 /* TrackerRadarKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA6B293F65D500E42796 /* TrackerRadarKit */; }; 3706FCAE293F65D500E42796 /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA6D293F65D500E42796 /* Lottie */; }; 3706FCAF293F65D500E42796 /* PrivacyDashboard in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA77293F65D500E42796 /* PrivacyDashboard */; }; - 3706FCB0293F65D500E42796 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 3706FA75293F65D500E42796 /* Common */; }; 3706FCB2293F65D500E42796 /* Fireproofing.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B02198425E05FAC00ED7DEA /* Fireproofing.storyboard */; }; 3706FCB3293F65D500E42796 /* Suggestion.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA80EC75256C46A2007083E7 /* Suggestion.storyboard */; }; 3706FCB4293F65D500E42796 /* CrashReports.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA693E5D2696E5B90007BB78 /* CrashReports.storyboard */; }; @@ -927,6 +925,15 @@ 37197EAC294244D600394917 /* FutureExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B634DBE6293C98C500C3C99E /* FutureExtension.swift */; }; 371C0A2927E33EDC0070591F /* FeedbackPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C0A2827E33EDC0070591F /* FeedbackPresenter.swift */; }; 371D00E129D8509400EC8598 /* OpenSSL in Frameworks */ = {isa = PBXBuildFile; productRef = 371D00E029D8509400EC8598 /* OpenSSL */; }; + 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 3722177F2B3337FE00B8E9C2 /* TestUtils */; }; + 372217822B33380700B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 372217812B33380700B8E9C2 /* TestUtils */; }; + 372217842B33380E00B8E9C2 /* TestUtils in Frameworks */ = {isa = PBXBuildFile; productRef = 372217832B33380E00B8E9C2 /* TestUtils */; }; + 37269EFB2B332F9E005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFA2B332F9E005E8E46 /* Common */; }; + 37269EFD2B332FAC005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFC2B332FAC005E8E46 /* Common */; }; + 37269EFF2B332FBB005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269EFE2B332FBB005E8E46 /* Common */; }; + 37269F012B332FC8005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269F002B332FC8005E8E46 /* Common */; }; + 37269F032B332FD8005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269F022B332FD8005E8E46 /* Common */; }; + 37269F052B3332C2005E8E46 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 37269F042B3332C2005E8E46 /* Common */; }; 372A0FEC2B2379310033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEB2B2379310033BF7F /* SyncMetricsEventsHandler.swift */; }; 372A0FED2B2379310033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEB2B2379310033BF7F /* SyncMetricsEventsHandler.swift */; }; 372A0FEE2B2379310033BF7F /* SyncMetricsEventsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 372A0FEB2B2379310033BF7F /* SyncMetricsEventsHandler.swift */; }; @@ -1089,7 +1096,6 @@ 4B2D062A2A11C0C900DE1F49 /* NetworkProtectionOptionKeyExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4D605F2A0B29FA00BCD287 /* NetworkProtectionOptionKeyExtension.swift */; }; 4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062B2A11C0E100DE1F49 /* Networking */; }; 4B2D062D2A11C12300DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 85799C1725DEBB3F0007EC87 /* Logging.swift */; }; - 4B2D06302A11C15900DE1F49 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B2D062F2A11C15900DE1F49 /* Common */; }; 4B2D06322A11C1D300DE1F49 /* NSApplicationExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA5C8F622591021700748EB7 /* NSApplicationExtension.swift */; }; 4B2D06332A11C1E300DE1F49 /* OptionalExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B637273C26CCF0C200C8CB02 /* OptionalExtension.swift */; }; 4B2D065B2A11D1FF00DE1F49 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B4BEC322A11B509001D9AC5 /* Logging.swift */; }; @@ -1228,7 +1234,6 @@ 4B8AC93D26B49BE600879451 /* FirefoxLoginReaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AC93C26B49BE600879451 /* FirefoxLoginReaderTests.swift */; }; 4B8AD0B127A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8AD0B027A86D9200AE44D6 /* WKWebsiteDataStoreExtensionTests.swift */; }; 4B8D9062276D1D880078DB17 /* LocaleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B8D9061276D1D880078DB17 /* LocaleExtension.swift */; }; - 4B8F52352A169D2D00BE7131 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B8F52342A169D2D00BE7131 /* Common */; }; 4B92928B26670D1700AD2C21 /* BookmarksOutlineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928526670D1600AD2C21 /* BookmarksOutlineView.swift */; }; 4B92928C26670D1700AD2C21 /* OutlineSeparatorViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928626670D1600AD2C21 /* OutlineSeparatorViewCell.swift */; }; 4B92928D26670D1700AD2C21 /* BookmarkOutlineViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B92928726670D1600AD2C21 /* BookmarkOutlineViewCell.swift */; }; @@ -1930,7 +1935,6 @@ 4B957BE72AC7AE700062CA31 /* SyncDataProviders in Frameworks */ = {isa = PBXBuildFile; productRef = 4B95793C2AC7AE700062CA31 /* SyncDataProviders */; }; 4B957BE82AC7AE700062CA31 /* SyncUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B9579362AC7AE700062CA31 /* SyncUI */; }; 4B957BE92AC7AE700062CA31 /* NetworkProtectionUI in Frameworks */ = {isa = PBXBuildFile; productRef = 4B95793D2AC7AE700062CA31 /* NetworkProtectionUI */; }; - 4B957BEA2AC7AE700062CA31 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 4B95792D2AC7AE700062CA31 /* Common */; }; 4B957BEB2AC7AE700062CA31 /* Persistence in Frameworks */ = {isa = PBXBuildFile; productRef = 4B9579312AC7AE700062CA31 /* Persistence */; }; 4B957BED2AC7AE700062CA31 /* Fireproofing.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4B02198425E05FAC00ED7DEA /* Fireproofing.storyboard */; }; 4B957BEE2AC7AE700062CA31 /* Suggestion.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA80EC75256C46A2007083E7 /* Suggestion.storyboard */; }; @@ -2722,6 +2726,13 @@ B65783E725F8AAFB00D8DB33 /* String+Punycode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B65783E625F8AAFB00D8DB33 /* String+Punycode.swift */; }; B657841A25FA484B00D8DB33 /* NSException+Catch.m in Sources */ = {isa = PBXBuildFile; fileRef = B657841925FA484B00D8DB33 /* NSException+Catch.m */; }; B657841F25FA497600D8DB33 /* NSException+Catch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B657841E25FA497600D8DB33 /* NSException+Catch.swift */; }; + B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CA2B316DF100A595BB /* SnapshotTesting */; }; + B65CD8CD2B316DFC00A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CC2B316DFC00A595BB /* SnapshotTesting */; }; + B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8CE2B316E0200A595BB /* SnapshotTesting */; }; + B65CD8D12B316E0C00A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8D02B316E0C00A595BB /* SnapshotTesting */; }; + B65CD8D32B316E1700A595BB /* SnapshotTesting in Frameworks */ = {isa = PBXBuildFile; productRef = B65CD8D22B316E1700A595BB /* SnapshotTesting */; }; + B65CD8D52B316FCA00A595BB /* __Snapshots__ in Resources */ = {isa = PBXBuildFile; fileRef = B65CD8D42B316FCA00A595BB /* __Snapshots__ */; }; + B65CD8D62B316FCA00A595BB /* __Snapshots__ in Resources */ = {isa = PBXBuildFile; fileRef = B65CD8D42B316FCA00A595BB /* __Snapshots__ */; }; B65DA5EF2A77CC3A00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E81F2A1E2603006D261F /* Bundle+NetworkProtectionExtensions.swift */; }; B65DA5F02A77CC3C00CBEE8D /* Bundle+NetworkProtectionExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B602E81F2A1E2603006D261F /* Bundle+NetworkProtectionExtensions.swift */; }; B65DA5F12A77D2BC00CBEE8D /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6106B9D26A565DA0013B453 /* BundleExtension.swift */; }; @@ -2933,6 +2944,8 @@ B6FA893D269C423100588ECD /* PrivacyDashboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */; }; B6FA893F269C424500588ECD /* PrivacyDashboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */; }; B6FA8941269C425400588ECD /* PrivacyDashboardPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */; }; + BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; + BB5789732B2CC0300009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */; }; BBDFDC5A2B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; }; BBDFDC5C2B2B8D7000F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; }; BBDFDC5D2B2B8E2100F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */; }; @@ -4069,6 +4082,8 @@ B657841825FA484B00D8DB33 /* NSException+Catch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSException+Catch.h"; sourceTree = ""; }; B657841925FA484B00D8DB33 /* NSException+Catch.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSException+Catch.m"; sourceTree = ""; }; B657841E25FA497600D8DB33 /* NSException+Catch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSException+Catch.swift"; sourceTree = ""; }; + B65CD8D42B316FCA00A595BB /* __Snapshots__ */ = {isa = PBXFileReference; lastKnownFileType = folder; path = __Snapshots__; sourceTree = ""; }; + B65CD8D72B341FD300A595BB /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; B65E6B9D26D9EC0800095F96 /* CircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgressView.swift; sourceTree = ""; }; B65E6B9F26D9F10600095F96 /* NSBezierPathExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSBezierPathExtension.swift; sourceTree = ""; }; B66260DC29AC5D4300E9E3EE /* NavigationProtectionTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationProtectionTabExtension.swift; sourceTree = ""; }; @@ -4224,6 +4239,7 @@ B6FA893C269C423100588ECD /* PrivacyDashboard.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = PrivacyDashboard.storyboard; sourceTree = ""; }; B6FA893E269C424500588ECD /* PrivacyDashboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardViewController.swift; sourceTree = ""; }; B6FA8940269C425400588ECD /* PrivacyDashboardPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyDashboardPopover.swift; sourceTree = ""; }; + BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionSubscriptionEventHandler.swift; sourceTree = ""; }; BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataBrokerProtectionExternalWaitlistPixels.swift; sourceTree = ""; }; CB24F70B29A3D9CB006DCC58 /* AppConfigurationURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfigurationURLProvider.swift; sourceTree = ""; }; CB6BCDF827C6BEFF00CC76DC /* PrivacyFeatures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacyFeatures.swift; sourceTree = ""; }; @@ -4269,11 +4285,13 @@ 984FD3BF299ACF35007334DD /* Bookmarks in Frameworks */, 37A5E2F0298AA1B20047046B /* Persistence in Frameworks */, 9DC70B1A2AA1FA5B005A844B /* LoginItems in Frameworks */, + 37269EFD2B332FAC005E8E46 /* Common in Frameworks */, 378F44E629B4BDEE00899924 /* SwiftUIExtensions in Frameworks */, 3706FCA7293F65D500E42796 /* BrowserServicesKit in Frameworks */, 3706FCA9293F65D500E42796 /* ContentBlocking in Frameworks */, 37F44A5F298C17830025E7FE /* Navigation in Frameworks */, B6EC37FF29B8D915001ACE79 /* Configuration in Frameworks */, + 372217822B33380700B8E9C2 /* TestUtils in Frameworks */, 3706FCAA293F65D500E42796 /* UserScript in Frameworks */, 3706FCAB293F65D500E42796 /* TrackerRadarKit in Frameworks */, 3739326529AE4B39009346AE /* DDGSync in Frameworks */, @@ -4281,7 +4299,6 @@ 3706FCAE293F65D500E42796 /* Lottie in Frameworks */, 37BA812F29B3CD6E0053F1A3 /* SyncUI in Frameworks */, 3706FCAF293F65D500E42796 /* PrivacyDashboard in Frameworks */, - 3706FCB0293F65D500E42796 /* Common in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4290,6 +4307,7 @@ buildActionMask = 2147483647; files = ( 3706FE88293F661700E42796 /* OHHTTPStubs in Frameworks */, + B65CD8CF2B316E0200A595BB /* SnapshotTesting in Frameworks */, 3706FE89293F661700E42796 /* OHHTTPStubsSwift in Frameworks */, 4B81AD372B29513100706C96 /* PixelKitTestingUtilities in Frameworks */, ); @@ -4299,6 +4317,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8D12B316E0C00A595BB /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4313,6 +4332,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8CD2B316DFC00A595BB /* SnapshotTesting in Frameworks */, B6AE39F329374AEC00C37AA4 /* OHHTTPStubs in Frameworks */, B6AE39F529374AEC00C37AA4 /* OHHTTPStubsSwift in Frameworks */, ); @@ -4322,7 +4342,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4B2D06302A11C15900DE1F49 /* Common in Frameworks */, + 37269F012B332FC8005E8E46 /* Common in Frameworks */, EE7295E92A545BC4008C0991 /* NetworkProtection in Frameworks */, 4B2537772A11BFE100610219 /* PixelKit in Frameworks */, 4B2D062C2A11C0E100DE1F49 /* Networking in Frameworks */, @@ -4361,6 +4381,7 @@ buildActionMask = 2147483647; files = ( EE7295EB2A545BFC008C0991 /* NetworkProtection in Frameworks */, + 37269F052B3332C2005E8E46 /* Common in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4368,7 +4389,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4B8F52352A169D2D00BE7131 /* Common in Frameworks */, + 37269EFF2B332FBB005E8E46 /* Common in Frameworks */, EE7295E72A545BBB008C0991 /* NetworkProtection in Frameworks */, 4B4D60982A0B2A5C00BCD287 /* PixelKit in Frameworks */, 4B4D60AF2A0C837F00BCD287 /* Networking in Frameworks */, @@ -4382,6 +4403,7 @@ files = ( 4B957BD52AC7AE700062CA31 /* QuickLookUI.framework in Frameworks */, 3143C8792B0D1F3D00382627 /* DataBrokerProtection in Frameworks */, + 372217842B33380E00B8E9C2 /* TestUtils in Frameworks */, 4B957BD62AC7AE700062CA31 /* LoginItems in Frameworks */, 4B957BD72AC7AE700062CA31 /* NetworkProtection in Frameworks */, 4B957BD82AC7AE700062CA31 /* BrowserServicesKit in Frameworks */, @@ -4403,9 +4425,9 @@ 4B957BE62AC7AE700062CA31 /* PrivacyDashboard in Frameworks */, 7B8C083C2AE1268E00F4C67F /* PixelKit in Frameworks */, 4B957BE72AC7AE700062CA31 /* SyncDataProviders in Frameworks */, + 37269F032B332FD8005E8E46 /* Common in Frameworks */, 4B957BE82AC7AE700062CA31 /* SyncUI in Frameworks */, 4B957BE92AC7AE700062CA31 /* NetworkProtectionUI in Frameworks */, - 4B957BEA2AC7AE700062CA31 /* Common in Frameworks */, 4B957BEB2AC7AE700062CA31 /* Persistence in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4421,6 +4443,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8D32B316E1700A595BB /* SnapshotTesting in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -4461,6 +4484,7 @@ 1E950E432912A10D0051A99B /* UserScript in Frameworks */, CBC83E3629B63D380008E19C /* Configuration in Frameworks */, 7B31FD8C2AD125620086AA24 /* NetworkProtectionIPC in Frameworks */, + 37269EFB2B332F9E005E8E46 /* Common in Frameworks */, 1E3ED4FD2AC1E0290075F60F /* Purchase in Frameworks */, 4B2AAAF529E70DEA0026AFC0 /* Lottie in Frameworks */, AA06B6B72672AF8100F541C5 /* Sparkle in Frameworks */, @@ -4471,8 +4495,8 @@ 1E950E412912A10D0051A99B /* PrivacyDashboard in Frameworks */, 37DF000529F9C056002B7D3E /* SyncDataProviders in Frameworks */, 37BA812D29B3CD690053F1A3 /* SyncUI in Frameworks */, + 372217802B3337FE00B8E9C2 /* TestUtils in Frameworks */, 4B4D60B12A0C83B900BCD287 /* NetworkProtectionUI in Frameworks */, - 1E25269C28F8741A00E44DFA /* Common in Frameworks */, 98A50964294B691800D10880 /* Persistence in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -4482,6 +4506,7 @@ buildActionMask = 2147483647; files = ( B6DA44172616C13800DD1EC2 /* OHHTTPStubs in Frameworks */, + B65CD8CB2B316DF100A595BB /* SnapshotTesting in Frameworks */, B6DA44192616C13800DD1EC2 /* OHHTTPStubsSwift in Frameworks */, 4B81AD352B29512B00706C96 /* PixelKitTestingUtilities in Frameworks */, ); @@ -4667,6 +4692,7 @@ 3199C6F82AF94F5B002A7BA1 /* DataBrokerProtectionFeatureDisabler.swift */, 3199C6FC2AF97367002A7BA1 /* DataBrokerProtectionAppEvents.swift */, BBDFDC592B2B8A0900F62D90 /* DataBrokerProtectionExternalWaitlistPixels.swift */, + BB5789712B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift */, ); path = DBP; sourceTree = ""; @@ -4717,6 +4743,7 @@ 373A1AA6283ECC8000586521 /* HTML */ = { isa = PBXGroup; children = ( + B65CD8D72B341FD300A595BB /* README.md */, 373A1AA7283ED1B900586521 /* BookmarkHTMLReader.swift */, 373A1AAF2842C4EA00586521 /* BookmarkHTMLImporter.swift */, ); @@ -5419,6 +5446,8 @@ 4B723DFE26B0003E00E14D75 /* DataImport */ = { isa = PBXGroup; children = ( + 37A803DA27FD69D300052F4C /* DataImportResources */, + B65CD8D42B316FCA00A595BB /* __Snapshots__ */, 373A1AB128451ED400586521 /* BookmarksHTMLImporterTests.swift */, 373A1AA9283ED86C00586521 /* BookmarksHTMLReaderTests.swift */, 4B3F641D27A8D3BD00E0C118 /* BrowserProfileTests.swift */, @@ -5435,7 +5464,6 @@ 4B8AC93C26B49BE600879451 /* FirefoxLoginReaderTests.swift */, 4BB99D0E26FE1A84001E4761 /* SafariBookmarksReaderTests.swift */, 4BF4951726C08395000547B8 /* ThirdPartyBrowserTests.swift */, - 37A803DA27FD69D300052F4C /* DataImportResources */, ); path = DataImport; sourceTree = ""; @@ -8002,7 +8030,6 @@ 3706FA6B293F65D500E42796 /* TrackerRadarKit */, 3706FA6D293F65D500E42796 /* Lottie */, 3706FA71293F65D500E42796 /* BrowserServicesKit */, - 3706FA75293F65D500E42796 /* Common */, 3706FA76293F65D500E42796 /* ContentBlocking */, 3706FA77293F65D500E42796 /* PrivacyDashboard */, 3706FA78293F65D500E42796 /* UserScript */, @@ -8016,6 +8043,8 @@ 37DF000629F9C061002B7D3E /* SyncDataProviders */, 9DC70B192AA1FA5B005A844B /* LoginItems */, 7B5F9A742AE2BE4E002AEBC0 /* PixelKit */, + 37269EFC2B332FAC005E8E46 /* Common */, + 372217812B33380700B8E9C2 /* TestUtils */, ); productName = DuckDuckGo; productReference = 3706FD05293F65D500E42796 /* DuckDuckGo App Store.app */; @@ -8040,6 +8069,7 @@ 3706FDD6293F661700E42796 /* OHHTTPStubs */, 3706FDD8293F661700E42796 /* OHHTTPStubsSwift */, 4B81AD362B29513100706C96 /* PixelKitTestingUtilities */, + B65CD8CE2B316E0200A595BB /* SnapshotTesting */, ); productName = DuckDuckGoTests; productReference = 3706FE99293F661700E42796 /* Unit Tests App Store.xctest */; @@ -8061,6 +8091,9 @@ 37079A95294236FA0031BB3C /* PBXTargetDependency */, ); name = "Integration Tests App Store"; + packageProductDependencies = ( + B65CD8D02B316E0C00A595BB /* SnapshotTesting */, + ); productName = "Integration Tests"; productReference = 3706FEB2293F662100E42796 /* Integration Tests App Store.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -8102,6 +8135,7 @@ packageProductDependencies = ( B6AE39F229374AEC00C37AA4 /* OHHTTPStubs */, B6AE39F429374AEC00C37AA4 /* OHHTTPStubsSwift */, + B65CD8CC2B316DFC00A595BB /* SnapshotTesting */, ); productName = "Integration Tests"; productReference = 4B1AD89D25FC27E200261379 /* Integration Tests.xctest */; @@ -8124,8 +8158,8 @@ packageProductDependencies = ( 4B2537762A11BFE100610219 /* PixelKit */, 4B2D062B2A11C0E100DE1F49 /* Networking */, - 4B2D062F2A11C15900DE1F49 /* Common */, EE7295E82A545BC4008C0991 /* NetworkProtection */, + 37269F002B332FC8005E8E46 /* Common */, ); productName = NetworkProtectionSystemExtension; productReference = 4B25375A2A11BE7300610219 /* com.duckduckgo.macos.vpn.network-extension.debug.systemextension */; @@ -8204,6 +8238,7 @@ name = DuckDuckGoNotifications; packageProductDependencies = ( EE7295EA2A545BFC008C0991 /* NetworkProtection */, + 37269F042B3332C2005E8E46 /* Common */, ); productName = DuckDuckGoNotifications; productReference = 4B4BEC202A11B4E2001D9AC5 /* DuckDuckGo Notifications.app */; @@ -8226,8 +8261,8 @@ packageProductDependencies = ( 4B4D60972A0B2A5C00BCD287 /* PixelKit */, 4B4D60AE2A0C837F00BCD287 /* Networking */, - 4B8F52342A169D2D00BE7131 /* Common */, EE7295E62A545BBB008C0991 /* NetworkProtection */, + 37269EFE2B332FBB005E8E46 /* Common */, ); productName = NetworkProtectionAppExtension; productReference = 4B4D603D2A0B290200BCD287 /* NetworkProtectionAppExtension.appex */; @@ -8256,7 +8291,6 @@ packageProductDependencies = ( 4B9579292AC7AE700062CA31 /* Sparkle */, 4B95792B2AC7AE700062CA31 /* BrowserServicesKit */, - 4B95792D2AC7AE700062CA31 /* Common */, 4B95792E2AC7AE700062CA31 /* ContentBlocking */, 4B95792F2AC7AE700062CA31 /* PrivacyDashboard */, 4B9579302AC7AE700062CA31 /* UserScript */, @@ -8279,6 +8313,8 @@ 7B8C083B2AE1268E00F4C67F /* PixelKit */, 7B31FD8F2AD1257B0086AA24 /* NetworkProtectionIPC */, 3143C8782B0D1F3D00382627 /* DataBrokerProtection */, + 37269F022B332FD8005E8E46 /* Common */, + 372217832B33380E00B8E9C2 /* TestUtils */, ); productName = DuckDuckGo; productReference = 4B957C412AC7AE700062CA31 /* DuckDuckGo Privacy Pro.app */; @@ -8316,6 +8352,9 @@ 7B4CE8E026F02108009134B1 /* PBXTargetDependency */, ); name = "UI Tests"; + packageProductDependencies = ( + B65CD8D22B316E1700A595BB /* SnapshotTesting */, + ); productName = "UI Tests"; productReference = 7B4CE8DA26F02108009134B1 /* UI Tests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; @@ -8390,7 +8429,6 @@ packageProductDependencies = ( AA06B6B62672AF8100F541C5 /* Sparkle */, 9807F644278CA16F00E1547B /* BrowserServicesKit */, - 1E25269B28F8741A00E44DFA /* Common */, 1E950E3E2912A10D0051A99B /* ContentBlocking */, 1E950E402912A10D0051A99B /* PrivacyDashboard */, 1E950E422912A10D0051A99B /* UserScript */, @@ -8413,6 +8451,8 @@ 7BA59C9A2AE18B49009A97B1 /* SystemExtensionManager */, 7B5DD6992AE51FFA001DE99C /* PixelKit */, 31A3A4E22B0C115F0021063C /* DataBrokerProtection */, + 37269EFA2B332F9E005E8E46 /* Common */, + 3722177F2B3337FE00B8E9C2 /* TestUtils */, ); productName = DuckDuckGo; productReference = AA585D7E248FD31100E9A3E2 /* DuckDuckGo.app */; @@ -8437,6 +8477,7 @@ B6DA44162616C13800DD1EC2 /* OHHTTPStubs */, B6DA44182616C13800DD1EC2 /* OHHTTPStubsSwift */, 4B81AD342B29512B00706C96 /* PixelKitTestingUtilities */, + B65CD8CA2B316DF100A595BB /* SnapshotTesting */, ); productName = DuckDuckGoTests; productReference = AA585D90248FD31400E9A3E2 /* Unit Tests.xctest */; @@ -8538,6 +8579,7 @@ B6EC37F529B5DAAC001ACE79 /* XCRemoteSwiftPackageReference "swifter" */, 371D00DF29D8509400EC8598 /* XCRemoteSwiftPackageReference "OpenSSL-XCFramework" */, 4B2AAAF329E70DEA0026AFC0 /* XCRemoteSwiftPackageReference "lottie-ios" */, + B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */, ); productRefGroup = AA585D7F248FD31100E9A3E2 /* Products */; projectDirPath = ""; @@ -8643,6 +8685,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + B65CD8D62B316FCA00A595BB /* __Snapshots__ in Resources */, 3706FE8B293F661700E42796 /* empty in Resources */, 376E2D282942843D001CD31B /* privacy-reference-tests in Resources */, 3706FE8C293F661700E42796 /* atb-with-update.json in Resources */, @@ -8884,6 +8927,7 @@ 31E163C0293A581900963C10 /* privacy-reference-tests in Resources */, B69B50542726CD8100758A2B /* atb-with-update.json in Resources */, 37A803DB27FD69D300052F4C /* DataImportResources in Resources */, + B65CD8D52B316FCA00A595BB /* __Snapshots__ in Resources */, B69B50522726CD8100758A2B /* atb.json in Resources */, 4B70C00127B0793D000386ED /* DuckDuckGo-ExampleCrash.ips in Resources */, 4BCF15ED2ABB9B180083F6DF /* network-protection-messages.json in Resources */, @@ -10910,6 +10954,7 @@ 4B957B8F2AC7AE700062CA31 /* Assertions.swift in Sources */, 4B957B902AC7AE700062CA31 /* BookmarkViewModel.swift in Sources */, 4B957B912AC7AE700062CA31 /* DaxSpeech.swift in Sources */, + BB5789732B2CC0300009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, 4B957B922AC7AE700062CA31 /* DuckPlayerSchemeHandler.swift in Sources */, 4B957B932AC7AE700062CA31 /* FirePopoverViewModel.swift in Sources */, 4B957B942AC7AE700062CA31 /* BWCommand.swift in Sources */, @@ -11478,6 +11523,7 @@ 4B9DB04A2A983B24000927DB /* NotificationService.swift in Sources */, 3775912D29AAC72700E26367 /* SyncPreferences.swift in Sources */, 1DB9618329F67F6200CF5568 /* FaviconNullStore.swift in Sources */, + BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, B693954F26F04BEB0015B914 /* PaddedImageButton.swift in Sources */, 4BA1A6B8258B081600F6F690 /* EncryptionKeyStoring.swift in Sources */, B65783E725F8AAFB00D8DB33 /* String+Punycode.swift in Sources */, @@ -12976,7 +13022,7 @@ repositoryURL = "https://github.com/duckduckgo/BrowserServicesKit"; requirement = { kind = exactVersion; - version = 95.0.0; + version = 98.0.1; }; }; AA06B6B52672AF8100F541C5 /* XCRemoteSwiftPackageReference "Sparkle" */ = { @@ -12987,6 +13033,14 @@ version = 2.5.1; }; }; + B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/pointfreeco/swift-snapshot-testing"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 1.15.1; + }; + }; B6DA44152616C13800DD1EC2 /* XCRemoteSwiftPackageReference "OHHTTPStubs" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/AliSoftware/OHHTTPStubs.git"; @@ -13006,11 +13060,6 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 1E25269B28F8741A00E44DFA /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 1E3ED4FC2AC1E0290075F60F /* Purchase */ = { isa = XCSwiftPackageProductDependency; productName = Purchase; @@ -13057,11 +13106,6 @@ package = 3706FA72293F65D500E42796 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = BrowserServicesKit; }; - 3706FA75293F65D500E42796 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 3706FA72293F65D500E42796 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 3706FA76293F65D500E42796 /* ContentBlocking */ = { isa = XCSwiftPackageProductDependency; package = 3706FA72293F65D500E42796 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -13092,6 +13136,51 @@ package = 371D00DF29D8509400EC8598 /* XCRemoteSwiftPackageReference "OpenSSL-XCFramework" */; productName = OpenSSL; }; + 3722177F2B3337FE00B8E9C2 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; + 372217812B33380700B8E9C2 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; + 372217832B33380E00B8E9C2 /* TestUtils */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = TestUtils; + }; + 37269EFA2B332F9E005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269EFC2B332FAC005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269EFE2B332FBB005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269F002B332FC8005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269F022B332FD8005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; + 37269F042B3332C2005E8E46 /* Common */ = { + isa = XCSwiftPackageProductDependency; + package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; + productName = Common; + }; 3739326429AE4B39009346AE /* DDGSync */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -13152,11 +13241,6 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = Networking; }; - 4B2D062F2A11C15900DE1F49 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 4B2D067E2A1334D700DE1F49 /* NetworkProtectionUI */ = { isa = XCSwiftPackageProductDependency; productName = NetworkProtectionUI; @@ -13202,11 +13286,6 @@ isa = XCSwiftPackageProductDependency; productName = PixelKitTestingUtilities; }; - 4B8F52342A169D2D00BE7131 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 4B9579292AC7AE700062CA31 /* Sparkle */ = { isa = XCSwiftPackageProductDependency; package = 4B95792A2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "Sparkle" */; @@ -13217,11 +13296,6 @@ package = 4B95792C2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = BrowserServicesKit; }; - 4B95792D2AC7AE700062CA31 /* Common */ = { - isa = XCSwiftPackageProductDependency; - package = 4B95792C2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; - productName = Common; - }; 4B95792E2AC7AE700062CA31 /* ContentBlocking */ = { isa = XCSwiftPackageProductDependency; package = 4B95792C2AC7AE700062CA31 /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; @@ -13471,6 +13545,31 @@ package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; productName = "plugin:SwiftLintPlugin"; }; + B65CD8CA2B316DF100A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8CC2B316DFC00A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8CE2B316E0200A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8D02B316E0C00A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; + B65CD8D22B316E1700A595BB /* SnapshotTesting */ = { + isa = XCSwiftPackageProductDependency; + package = B65CD8C92B316DF100A595BB /* XCRemoteSwiftPackageReference "swift-snapshot-testing" */; + productName = SnapshotTesting; + }; B692D0DE2B209FB7003F2548 /* SwiftLintPlugin */ = { isa = XCSwiftPackageProductDependency; package = 9807F643278CA16F00E1547B /* XCRemoteSwiftPackageReference "BrowserServicesKit" */; diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 301109ce38..13547f23d7 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/duckduckgo/BrowserServicesKit", "state" : { - "revision" : "ae9e9180f74d92c83fc3cc1d2fc23f4855fb361c", - "version" : "95.0.0" + "revision" : "ea133abe237b6cb57a4237e0373318a40c10afc2", + "version" : "98.0.1" } }, { @@ -108,6 +108,24 @@ "version" : "1.3.0" } }, + { + "identity" : "swift-snapshot-testing", + "kind" : "remoteSourceControl", + "location" : "https://github.com/pointfreeco/swift-snapshot-testing", + "state" : { + "revision" : "59b663f68e69f27a87b45de48cb63264b8194605", + "version" : "1.15.1" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "6ad4ea24b01559dde0773e3d091f1b9e36175036", + "version" : "509.0.2" + } + }, { "identity" : "swifter", "kind" : "remoteSourceControl", @@ -129,7 +147,7 @@ { "identity" : "trackerradarkit", "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit.git", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", "state" : { "revision" : "a6b7ba151d9dc6684484f3785293875ec01cc1ff", "version" : "1.2.2" diff --git a/DuckDuckGo/Application/AppDelegate.swift b/DuckDuckGo/Application/AppDelegate.swift index 8d21c98b97..545b0e8095 100644 --- a/DuckDuckGo/Application/AppDelegate.swift +++ b/DuckDuckGo/Application/AppDelegate.swift @@ -69,10 +69,15 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel private(set) var syncDataProviders: SyncDataProviders! private(set) var syncService: DDGSyncing? private var isSyncInProgressCancellable: AnyCancellable? + private var syncFeatureFlagsCancellable: AnyCancellable? private var screenLockedCancellable: AnyCancellable? private var emailCancellables = Set() let bookmarksManager = LocalBookmarkManager.shared +#if DBP && SUBSCRIPTION + private let dataBrokerProtectionSubscriptionEventHandler = DataBrokerProtectionSubscriptionEventHandler() +#endif + private var didFinishLaunching = false #if SPARKLE @@ -242,6 +247,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel UNUserNotificationCenter.current().delegate = self #endif +#if DBP && SUBSCRIPTION + dataBrokerProtectionSubscriptionEventHandler.registerForSubscriptionAccountManagerEvents() +#endif + #if DBP DataBrokerProtectionAppEvents().applicationDidFinishLaunching() #endif @@ -339,7 +348,13 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel let environment = defaultEnvironment #endif let syncDataProviders = SyncDataProviders(bookmarksDatabase: BookmarkDatabase.shared.db) - let syncService = DDGSync(dataProvidersSource: syncDataProviders, errorEvents: SyncErrorHandler(), log: OSLog.sync, environment: environment) + let syncService = DDGSync( + dataProvidersSource: syncDataProviders, + errorEvents: SyncErrorHandler(), + privacyConfigurationManager: ContentBlocking.shared.privacyConfigurationManager, + log: OSLog.sync, + environment: environment + ) syncService.initializeIfNeeded() syncDataProviders.setUpDatabaseCleaners(syncService: syncService) @@ -356,12 +371,43 @@ final class AppDelegate: NSObject, NSApplicationDelegate, FileDownloadManagerDel isSyncInProgressCancellable = syncService.isSyncInProgressPublisher .filter { $0 } .asVoid() - .prefix(1) - .sink { + .sink { [weak syncService] in Pixel.fire(.syncDaily, limitTo: .dailyFirst) + syncService?.syncDailyStats.sendStatsIfNeeded(handler: { params in + Pixel.fire(.syncSuccessRateDaily, withAdditionalParameters: params) + }) } subscribeSyncQueueToScreenLockedNotifications() + subscribeToSyncFeatureFlags(syncService) + } + + @UserDefaultsWrapper(key: .syncDidShowSyncPausedByFeatureFlagAlert, defaultValue: false) + private var syncDidShowSyncPausedByFeatureFlagAlert: Bool + + private func subscribeToSyncFeatureFlags(_ syncService: DDGSync) { + syncFeatureFlagsCancellable = syncService.featureFlagsPublisher + .dropFirst() + .map { $0.contains(.dataSyncing) } + .receive(on: DispatchQueue.main) + .sink { [weak self, weak syncService] isDataSyncingAvailable in + if isDataSyncingAvailable { + self?.syncDidShowSyncPausedByFeatureFlagAlert = false + } else if syncService?.authState == .active, self?.syncDidShowSyncPausedByFeatureFlagAlert == false { + let isSyncUIVisible = syncService?.featureFlags.contains(.userInterface) == true + let alert = NSAlert.dataSyncingDisabledByFeatureFlag(showLearnMore: isSyncUIVisible) + let response = alert.runModal() + self?.syncDidShowSyncPausedByFeatureFlagAlert = true + + switch response { + case .alertSecondButtonReturn: + alert.window.sheetParent?.endSheet(alert.window) + WindowControllersManager.shared.showPreferencesTab(withSelectedPane: .sync) + default: + break + } + } + } } private func subscribeSyncQueueToScreenLockedNotifications() { diff --git a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift index 7ea5d2b43a..b955524cac 100644 --- a/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift +++ b/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift @@ -279,9 +279,6 @@ final class LocalBookmarkStore: BookmarkStore { // will return the children of the root folder, as the root folder is an implementation detail of the bookmarks store. let rootFolder = self.bookmarksRoot(in: context) let orphanedEntities = BookmarkUtils.fetchOrphanedEntities(context) - if !orphanedEntities.isEmpty { - self.reportOrphanedBookmarksIfNeeded() - } results = (rootFolder?.childrenArray ?? []) + orphanedEntities case .favorites: results = self.favoritesRoot(in: context)?.favoritesArray ?? [] @@ -300,15 +297,6 @@ final class LocalBookmarkStore: BookmarkStore { } } - private func reportOrphanedBookmarksIfNeeded() { - Task { @MainActor in - guard let syncService = NSApp.delegateTyped.syncService, syncService.authState == .inactive else { - return - } - Pixel.fire(.debug(event: .orphanedBookmarksPresent)) - } - } - func save(bookmark: Bookmark, parent: BookmarkFolder?, index: Int?, completion: @escaping (Bool, Error?) -> Void) { applyChangesAndSave(changes: { [weak self] context in guard let self = self else { diff --git a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift index 06d86aa2fc..628ddeddfc 100644 --- a/DuckDuckGo/Common/Extensions/NSAlertExtension.swift +++ b/DuckDuckGo/Common/Extensions/NSAlertExtension.swift @@ -221,6 +221,18 @@ extension NSAlert { return alert } + static func dataSyncingDisabledByFeatureFlag(showLearnMore: Bool, upgradeRequired: Bool = false) -> NSAlert { + let alert = NSAlert() + alert.messageText = UserText.syncPausedTitle + alert.informativeText = upgradeRequired ? UserText.syncUnavailableMessageUpgradeRequired : UserText.syncUnavailableMessage + alert.alertStyle = .warning + alert.addButton(withTitle: UserText.ok) + if showLearnMore { + alert.addButton(withTitle: UserText.learnMore) + } + return alert + } + static func customConfigurationAlert(configurationUrl: String) -> NSAlert { let alert = NSAlert() alert.messageText = "Set custom configuration URL:" diff --git a/DuckDuckGo/Common/Localizables/UserText.swift b/DuckDuckGo/Common/Localizables/UserText.swift index d4084d6d47..0eb91fe989 100644 --- a/DuckDuckGo/Common/Localizables/UserText.swift +++ b/DuckDuckGo/Common/Localizables/UserText.swift @@ -503,6 +503,9 @@ struct UserText { static let syncBookmarkPausedAlertDescription = NSLocalizedString("alert.sync-bookmarks-paused-description", value: "You have exceeded the bookmarks sync limit. Try deleting some bookmarks. Until this is resolved your bookmarks will not be backed up.", comment: "Description for alert shown when sync bookmarks paused for too many items") static let syncCredentialsPausedAlertTitle = NSLocalizedString("alert.sync-credentials-paused-title", value: "Passwords Sync is Paused", comment: "Title for alert shown when sync credentials paused for too many items") static let syncCredentialsPausedAlertDescription = NSLocalizedString("alert.sync-credentials-paused-description", value: "You have exceeded the passwords sync limit. Try deleting some passwords. Until this is resolved your passwords will not be backed up.", comment: "Description for alert shown when sync credentials paused for too many items") + static let syncPausedTitle = NSLocalizedString("alert.sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("alert.sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("alert.sync.warning.data-syncing-disabled-upgrade-required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") static let defaultBrowser = NSLocalizedString("preferences.default-browser", value: "Default Browser", comment: "Show default browser preferences") static let appearance = NSLocalizedString("preferences.appearance", value: "Appearance", comment: "Show appearance preferences") static let privacy = NSLocalizedString("preferences.privacy", value: "Privacy", comment: "Show privacy browser preferences") diff --git a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift index 7bcf40d4b8..3204cde614 100644 --- a/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift +++ b/DuckDuckGo/Common/Utilities/UserDefaultsWrapper.swift @@ -41,6 +41,8 @@ public struct UserDefaultsWrapper { case configStoragePrivacyConfigurationEtag = "config.storage.privacyconfiguration.etag" case configFBConfigEtag = "config.storage.fbconfig.etag" + case configLastInstalled = "config.last.installed" + case fireproofDomains = "com.duckduckgo.fireproofing.allowedDomains" case areDomainsMigratedToETLDPlus1 = "com.duckduckgo.are-domains-migrated-to-etldplus1" case unprotectedDomains = "com.duckduckgo.contentblocker.unprotectedDomains" @@ -176,6 +178,7 @@ public struct UserDefaultsWrapper { case syncIsEligibleForFaviconsFetcherOnboarding = "sync.is-eligible-for-favicons-fetcher-onboarding" case syncDidPresentFaviconsFetcherOnboarding = "sync.did-present-favicons-fetcher-onboarding" case syncDidMigrateToImprovedListsHandling = "sync.did-migrate-to-improved-lists-handling" + case syncDidShowSyncPausedByFeatureFlagAlert = "sync.did-show-sync-paused-by-feature-flag-alert" } enum RemovedKeys: String, CaseIterable { diff --git a/DuckDuckGo/Configuration/ConfigurationManager.swift b/DuckDuckGo/Configuration/ConfigurationManager.swift index a486bb4365..a64673dfc0 100644 --- a/DuckDuckGo/Configuration/ConfigurationManager.swift +++ b/DuckDuckGo/Configuration/ConfigurationManager.swift @@ -61,6 +61,9 @@ final class ConfigurationManager { @UserDefaultsWrapper(key: .configLastUpdated, defaultValue: .distantPast) private(set) var lastUpdateTime: Date + @UserDefaultsWrapper(key: .configLastInstalled, defaultValue: nil) + private(set) var lastConfigurationInstallDate: Date? + private var timerCancellable: AnyCancellable? private var lastRefreshCheckTime: Date = Date() @@ -97,9 +100,9 @@ final class ConfigurationManager { os_log("last refresh check %{public}s", log: .config, type: .default, String(describing: lastRefreshCheckTime)) } - private func refreshNow() async { + private func refreshNow(isDebug: Bool = false) async { let updateTrackerBlockingDependenciesTask = Task { - let didFetchAnyTrackerBlockingDependencies = await fetchTrackerBlockingDependencies() + let didFetchAnyTrackerBlockingDependencies = await fetchTrackerBlockingDependencies(isDebug: isDebug) if didFetchAnyTrackerBlockingDependencies { updateTrackerBlockingDependencies() tryAgainLater() @@ -134,13 +137,13 @@ final class ConfigurationManager { log() } - private func fetchTrackerBlockingDependencies() async -> Bool { + private func fetchTrackerBlockingDependencies(isDebug: Bool) async -> Bool { var didFetchAnyTrackerBlockingDependencies = false var tasks = [Configuration: Task<(), Swift.Error>]() tasks[.trackerDataSet] = Task { try await fetcher.fetch(.trackerDataSet) } tasks[.surrogates] = Task { try await fetcher.fetch(.surrogates) } - tasks[.privacyConfiguration] = Task { try await fetcher.fetch(.privacyConfiguration) } + tasks[.privacyConfiguration] = Task { try await fetcher.fetch(.privacyConfiguration, isDebug: isDebug) } for (configuration, task) in tasks { do { @@ -184,9 +187,9 @@ final class ConfigurationManager { private var isReadyToRefresh: Bool { Date().timeIntervalSince(lastUpdateTime) > Constants.refreshPeriodSeconds } - public func forceRefresh() { + public func forceRefresh(isDebug: Bool = false) { Task { - await refreshNow() + await refreshNow(isDebug: isDebug) } } @@ -200,6 +203,7 @@ final class ConfigurationManager { } private func updateTrackerBlockingDependencies() { + lastConfigurationInstallDate = Date() ContentBlocking.shared.trackerDataManager.reload(etag: ConfigurationStore.shared.loadEtag(for: .trackerDataSet), data: ConfigurationStore.shared.loadData(for: .trackerDataSet)) ContentBlocking.shared.privacyConfigurationManager.reload(etag: ConfigurationStore.shared.loadEtag(for: .privacyConfiguration), diff --git a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift index fb7b34b26e..f0e8b44787 100644 --- a/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift +++ b/DuckDuckGo/ContentBlocker/AppPrivacyConfigurationDataProvider.swift @@ -22,8 +22,8 @@ import BrowserServicesKit final class AppPrivacyConfigurationDataProvider: EmbeddedDataProvider { public struct Constants { - public static let embeddedDataETag = "\"ca66d409eb00e5c19f3a0abae449dd1a\"" - public static let embeddedDataSHA = "42f9d3064372bc85ac8ee37afe883ed4741d6a3cfcb9ce927c2f732c3f694140" + public static let embeddedDataETag = "\"8e38db3f042f4a2a5c9f7ca8d33f17c9\"" + public static let embeddedDataSHA = "a9fc8ee927a37d1b5545377ff29de798f3ec3b8a757430c3af72d13a3844c258" } var embeddedDataEtag: String { diff --git a/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift index df918a336e..f8dc30a6ae 100644 --- a/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift +++ b/DuckDuckGo/ContentBlocker/Mocks/MockPrivacyConfiguration.swift @@ -51,6 +51,16 @@ final class MockPrivacyConfiguration: PrivacyConfiguration { func userDisabledProtection(forDomain: String) {} } +final class MockInternalUserStoring: InternalUserStoring { + var isInternalUser: Bool = false +} + +extension DefaultInternalUserDecider { + convenience init(mockedStore: MockInternalUserStoring = MockInternalUserStoring()) { + self.init(store: mockedStore) + } +} + final class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManaging { var embeddedConfigData: BrowserServicesKit.PrivacyConfigurationManager.ConfigurationData { fatalError("not implemented") @@ -70,6 +80,7 @@ final class MockPrivacyConfigurationManager: NSObject, PrivacyConfigurationManag var updatesPublisher: AnyPublisher = Just(()).eraseToAnyPublisher() var privacyConfig: PrivacyConfiguration = MockPrivacyConfiguration() + var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider() } #endif diff --git a/DuckDuckGo/ContentBlocker/macos-config.json b/DuckDuckGo/ContentBlocker/macos-config.json index 94914fa75a..6ec6c5ecf4 100644 --- a/DuckDuckGo/ContentBlocker/macos-config.json +++ b/DuckDuckGo/ContentBlocker/macos-config.json @@ -1,6 +1,6 @@ { "readme": "https://github.com/duckduckgo/privacy-configuration", - "version": 1702579565498, + "version": 1703003631137, "features": { "adClickAttribution": { "readme": "https://help.duckduckgo.com/duckduckgo-help-pages/privacy/web-tracking-protections/#3rd-party-tracker-loading-protection", @@ -245,6 +245,9 @@ { "domain": "metro.co.uk" }, + { + "domain": "youtube.com" + }, { "domain": "earth.google.com" }, @@ -264,7 +267,7 @@ ] }, "state": "enabled", - "hash": "9ab9e1acdb6a8617c77109acc1e3943c" + "hash": "a1060783f1bf56f5cc661b994c9c9e56" }, "autofill": { "exceptions": [ @@ -1030,12 +1033,16 @@ { "domain": "flysas.com", "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1347" + }, + { + "domain": "facebook.com", + "reason": "https://github.com/duckduckgo/privacy-configuration/issues/1641" } ] }, "exceptions": [], "state": "enabled", - "hash": "d757f6509e9a9a20140c755ed0f21ea2" + "hash": "6ed03febdd780fd845cc1f7d6ce74ecf" }, "dbp": { "state": "enabled", @@ -2014,6 +2021,23 @@ } ] }, + { + "domain": "dexerto.com", + "rules": [ + { + "selector": "#leaderboard-top-adhesion", + "type": "closest-empty" + }, + { + "selector": "[data-cy='Ad']", + "type": "closest-empty" + }, + { + "selector": "[data-cy='VidazooPlayer']", + "type": "closest-empty" + } + ] + }, { "domain": "dpreview.com", "rules": [ @@ -3678,7 +3702,7 @@ ] }, "state": "enabled", - "hash": "c34713afaf78e090b587a923f132ed56" + "hash": "8551081ffe228a4bef49deba3fe37b19" }, "exceptionHandler": { "exceptions": [ @@ -3933,10 +3957,13 @@ }, { "domain": "airbnb.com.br" + }, + { + "domain": "zoom.us" } ], "state": "enabled", - "hash": "75e673ce365430652fc7cc896ed49a5f" + "hash": "82ebfddf791575ada285a49b7fdc71ca" }, "fingerprintingScreenSize": { "settings": { @@ -4460,6 +4487,11 @@ "state": "disabled", "hash": "5e792dd491428702bc0104240fbce0ce" }, + "sync": { + "exceptions": [], + "state": "disabled", + "hash": "728493ef7a1488e4781656d3f9db84aa" + }, "trackerAllowlist": { "state": "enabled", "settings": { @@ -4750,8 +4782,7 @@ { "rule": "analytics.analytics-egain.com/onetag/", "domains": [ - "landsend.com", - "support.norton.com" + "" ] } ] @@ -5323,6 +5354,7 @@ "asics.com", "brooklinen.com", "carters.com", + "otterbox.com", "seatosummit.com" ] } @@ -5382,6 +5414,16 @@ } ] }, + "egain.cloud": { + "rules": [ + { + "rule": "egain.cloud/", + "domains": [ + "" + ] + } + ] + }, "ensighten.com": { "rules": [ { @@ -5509,8 +5551,7 @@ { "rule": "app.five9.com", "domains": [ - "gmsdnv.com", - "machiassavings.bank" + "" ] } ] @@ -5750,6 +5791,12 @@ "rawstory.com", "usatoday.com" ] + }, + { + "rule": "storage.googleapis.com/code.snapengage.com/", + "domains": [ + "" + ] } ] }, @@ -5977,15 +6024,9 @@ "gorgias.chat": { "rules": [ { - "rule": "config.gorgias.chat", + "rule": "gorgias.chat", "domains": [ - "help.athleticbrewing.com" - ] - }, - { - "rule": "assets.gorgias.chat", - "domains": [ - "help.athleticbrewing.com" + "" ] } ] @@ -7509,7 +7550,7 @@ "domain": "sundancecatalog.com" } ], - "hash": "5cecb6d28193f468b28b9afdaca04da1" + "hash": "b6996e16fe1785f6d75cd6302da1f48e" }, "trackingCookies1p": { "settings": { diff --git a/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift new file mode 100644 index 0000000000..783c900d12 --- /dev/null +++ b/DuckDuckGo/DBP/DataBrokerProtectionSubscriptionEventHandler.swift @@ -0,0 +1,55 @@ +// +// DataBrokerProtectionSubscriptionEventHandler.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#if DBP && SUBSCRIPTION + +import Foundation +import Account +import DataBrokerProtection + +final class DataBrokerProtectionSubscriptionEventHandler { + + private let accountManager: Account.AccountManaging + private let authRepository: AuthenticationRepository + + init(accountManager: Account.AccountManaging = Account.AccountManager(), + authRepository: AuthenticationRepository = KeychainAuthenticationData()) { + self.accountManager = accountManager + self.authRepository = authRepository + } + + func registerForSubscriptionAccountManagerEvents() { + NotificationCenter.default.addObserver(self, selector: #selector(handleAccountDidSignIn), name: .accountDidSignIn, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleAccountDidSignOut), name: .accountDidSignOut, object: nil) + } + + @objc private func handleAccountDidSignIn() { + guard let token = accountManager.token else { + Pixel.fire(.dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn) + assertionFailure("[DBP Subscription] AccountManager signed in but token could not be retrieved") + return + } + + authRepository.save(accessToken: token) + } + + @objc private func handleAccountDidSignOut() { + // Going to be defined here: https://app.asana.com/0/1204006570077678/1206206961074916/f + } +} + +#endif diff --git a/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift b/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift index 67358f32e6..62275a5c10 100644 --- a/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift +++ b/DuckDuckGo/DataImport/Bookmarks/Chromium/ImportedBookmarks.swift @@ -18,13 +18,13 @@ import Foundation -struct ImportedBookmarks: Decodable { +struct ImportedBookmarks: Codable, Equatable { - struct BookmarkOrFolder: Decodable { + struct BookmarkOrFolder: Codable, Equatable { let name: String let type: String let urlString: String? - let isDDGFavorite: Bool + var isDDGFavorite: Bool = false let children: [BookmarkOrFolder]? @@ -62,15 +62,6 @@ struct ImportedBookmarks: Decodable { case children } - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - name = try container.decode(String.self, forKey: .name) - type = try container.decode(String.self, forKey: .type) - urlString = try container.decodeIfPresent(String.self, forKey: .urlString) - children = try container.decodeIfPresent([BookmarkOrFolder].self, forKey: .children) - isDDGFavorite = false - } - init(name: String, type: String, urlString: String?, children: [BookmarkOrFolder]?, isDDGFavorite: Bool = false) { self.name = name.trimmingWhitespace() self.type = type @@ -80,7 +71,7 @@ struct ImportedBookmarks: Decodable { } } - struct TopLevelFolders: Decodable { + struct TopLevelFolders: Codable, Equatable { let bookmarkBar: BookmarkOrFolder let otherBookmarks: BookmarkOrFolder diff --git a/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift b/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift index 7ab2b8e5a8..31dbf918f7 100644 --- a/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift +++ b/DuckDuckGo/DataImport/Bookmarks/HTML/BookmarkHTMLReader.swift @@ -26,15 +26,14 @@ struct HTMLImportedBookmarks { final class BookmarkHTMLReader { struct ImportError: DataImportError { + // !! do not change the order + // cases 2,3 and 6 are reserved for removed errors enum OperationType: Int { - case parseXml - case validationBody - case validationH1 - case findTopLevelFolder - case proceedToTopLevelFolder - case readFolder - case bookmarkRead - case unknown + case parseXml = 0 + case validationBody = 1 + case proceedToTopLevelFolder = 4 + case readFolder = 5 + case unknown = 7 } var action: DataImportAction { .bookmarks } @@ -118,10 +117,6 @@ final class BookmarkHTMLReader { // MARK: - Private - private enum ReaderError: Error { - case noTopLevelFolder - } - private func determineImportSource(_ cursor: inout XMLNode?) throws -> BookmarkImportSource? { let isInSafariFormat = try findTopLevelFolderNameNode(&cursor) @@ -139,56 +134,51 @@ final class BookmarkHTMLReader { private func validateHTMLBookmarksDocument(_ document: XMLDocument) throws -> XMLNode? { let root = document.rootElement() guard let body = root?.child(at: 1) else { throw ImportError(type: .validationBody, underlyingError: nil) } - + // get /html/body/*[0] let cursor = body.child(at: 0) - guard cursor?.htmlTag == .h1 else { throw ImportError(type: .validationH1, underlyingError: nil) } return cursor } private func findTopLevelFolderNameNode(_ cursor: inout XMLNode?) throws -> Bool { var isInSafariFormat = false - cursor = cursor?.nextSibling - switch cursor?.htmlTag { - case .dl: - do { - try proceedToTopLevelFolderNameNode(&cursor) - } catch ReaderError.noTopLevelFolder { + rootLoop: while cursor != nil { + switch cursor?.htmlTag { + case .dl: + let originalCursorValue = cursor + cursor = cursor?.child(at: 0) + dlLoop: while cursor != nil { + switch cursor?.htmlTag { + case .dd: + if cursor?.child(at: 0)?.htmlTag == .h3 { + cursor = cursor?.child(at: 0) + break dlLoop + } + cursor = cursor?.nextSibling + case .dt: + // There is no "top-level" folder, and the first item + // in the bookmarks file is a regular bookmark, not a folder. + // This is specific to iOS and MacOS DuckDuckGo apps. + cursor = originalCursorValue + isInSafariFormat = true + break dlLoop + default: + throw ImportError(type: .proceedToTopLevelFolder, underlyingError: nil) + } + } + break rootLoop + case .h3: isInSafariFormat = true + break rootLoop + default: + cursor = cursor?.nextSibling } - case .h3: - isInSafariFormat = true - default: - throw ImportError(type: .findTopLevelFolder, underlyingError: nil) } return isInSafariFormat } - private func proceedToTopLevelFolderNameNode(_ cursor: inout XMLNode?) throws { - let originalCursorValue = cursor - cursor = cursor?.child(at: 0) - while cursor != nil { - guard cursor?.htmlTag == .dd else { - if cursor?.htmlTag == .dt { - // There is no "top-level" folder, and the first item - // in the bookmarks file is a regular bookmark, not a folder. - // This is specific to iOS and MacOS DuckDuckGo apps. - cursor = originalCursorValue - throw ReaderError.noTopLevelFolder - } else { - throw ImportError(type: .proceedToTopLevelFolder, underlyingError: nil) - } - } - if cursor?.child(at: 0)?.htmlTag == .h3 { - cursor = cursor?.child(at: 0) - return - } - cursor = cursor?.nextSibling - } - } - private func findNextItem(_ cursor: inout XMLNode?) -> XMLNode.BookmarkItemType? { var itemType: XMLNode.BookmarkItemType? cursor = cursor?.parent?.nextSibling @@ -220,7 +210,7 @@ final class BookmarkHTMLReader { private func readItem(_ item: XMLNode.BookmarkItemType, at cursor: XMLNode?) throws -> [ImportedBookmarks.BookmarkOrFolder] { switch item { case .bookmark: - return [try readBookmark(cursor)] + return readBookmark(cursor).map { [$0] } ?? [] case .folder: return [try readFolder(cursor)] case .safariTopLevelBookmarks: @@ -255,7 +245,9 @@ final class BookmarkHTMLReader { case (.dd, .h3): children.append(try readFolder(firstChild)) case (.dt, .a): - children.append(try readBookmark(firstChild)) + if let bookmark = readBookmark(firstChild) { + children.append(bookmark) + } default: break } @@ -266,9 +258,8 @@ final class BookmarkHTMLReader { return children } - private func readBookmark(_ node: XMLNode?) throws -> ImportedBookmarks.BookmarkOrFolder { - guard let bookmark = node?.bookmark else { throw ImportError(type: .bookmarkRead, underlyingError: nil) } - return bookmark + private func readBookmark(_ node: XMLNode?) -> ImportedBookmarks.BookmarkOrFolder? { + return node?.bookmark } private let bookmarksFileURL: URL @@ -369,14 +360,19 @@ private extension XMLNode { } var bookmark: ImportedBookmarks.BookmarkOrFolder? { - guard htmlTag == .a, let name = text else { + guard htmlTag == .a, + let name = text, + let element = self as? XMLElement else { return nil } + + if element.stringValue == "---" && element.attribute(forName: "href")?.stringValue == "http://bookmark.placeholder.url/" { + // vivaldi separator markup return nil } return .bookmark( name: name, - urlString: (self as? XMLElement)?.attribute(forName: "href")?.stringValue, - isDDGFavorite: (self as? XMLElement)?.attribute(forName: "duckduckgo:favorite")?.stringValue == "true" + urlString: element.attribute(forName: "href")?.stringValue, + isDDGFavorite: element.attribute(forName: "duckduckgo:favorite")?.stringValue == "true" ) } } diff --git a/DuckDuckGo/DataImport/Bookmarks/HTML/README.md b/DuckDuckGo/DataImport/Bookmarks/HTML/README.md index a44dbc4a4a..a8944c84e6 100644 --- a/DuckDuckGo/DataImport/Bookmarks/HTML/README.md +++ b/DuckDuckGo/DataImport/Bookmarks/HTML/README.md @@ -4,15 +4,24 @@ ```mermaid graph TD - A[cursor = document root node] -->|validateHTMLBookmarksDocument| B[cursor = h1 tag] + A[cursor = document root node] -->|validateHTMLBookmarksDocument| B[cursor = first body child] B --> C[determineImportSource
findTopLevelFolderNameNode] - C --> D[cursor => nextSibling] + C --> D{cursor.htmlTag} D -->|`dl` tag| E[proceedToTopLevel
FolderNameNode] - E --> F[store cursor
cursor => firstChild] - F -->|`dd` tag| G[cursor => firstChild] + E --> E1[store cursor
cursor => firstChild] + E1 --> F{cursor.htmlTag} + F -->|`dd` tag| F1[cursor => firstChild] + F1 --> G{cursor.htmlTag} G -->|`h3` tag| H[cursor = top level folder name
source = other] + G -->|other tag| G1[cursor => nextSibling] + G -->|`nil`| RET + G1 --> F F -->|`dt` tag| I[restore cursor
no top level folder] + F -->|other tag| ERR D -->|`h3` tag| J{isDDGBookmarks?} + D -->|other tag| NXT[cursor => nextSibling] + NXT --> D + D -->|`nil`| RET I --> K{isDDGBookmarks?} K -->|no| L[cursor = first item in folder
source = Safari] K -->|yes| M[cursor = first item in folder
source = DDG WebKit] diff --git a/DuckDuckGo/Menus/MainMenu.swift b/DuckDuckGo/Menus/MainMenu.swift index dc532e19f3..9c3fdd9ac3 100644 --- a/DuckDuckGo/Menus/MainMenu.swift +++ b/DuckDuckGo/Menus/MainMenu.swift @@ -643,8 +643,14 @@ import Subscription } private func updateRemoteConfigurationInfo() { - let dateString = DateFormatter.localizedString(from: ConfigurationManager.shared.lastUpdateTime, dateStyle: .short, timeStyle: .medium) - configurationDateAndTimeMenuItem.title = "Last Update Time: \(dateString)" + var dateString: String + if let date = ConfigurationManager.shared.lastConfigurationInstallDate { + dateString = DateFormatter.localizedString(from: date, dateStyle: .short, timeStyle: .medium) + configurationDateAndTimeMenuItem.title = "Last Update Time: \(dateString)" + } else { + dateString = "Last Update Time: -" + } + configurationDateAndTimeMenuItem.title = dateString customConfigurationUrlMenuItem.title = "Configuration URL: \(AppConfigurationURLProvider().url(for: .privacyConfiguration).absoluteString)" } diff --git a/DuckDuckGo/Menus/MainMenuActions.swift b/DuckDuckGo/Menus/MainMenuActions.swift index 8a30695b31..4a4c4e1f7d 100644 --- a/DuckDuckGo/Menus/MainMenuActions.swift +++ b/DuckDuckGo/Menus/MainMenuActions.swift @@ -739,7 +739,7 @@ extension MainViewController { @objc func reloadConfigurationNow(_ sender: Any?) { OSLog.loggingCategories.insert(OSLog.AppCategories.config.rawValue) - ConfigurationManager.shared.forceRefresh() + ConfigurationManager.shared.forceRefresh(isDebug: true) } private func setConfigurationUrl(_ configurationUrl: URL?) { @@ -750,7 +750,7 @@ extension MainViewController { configurationProvider.resetToDefaultConfigurationUrl() } Configuration.setURLProvider(configurationProvider) - ConfigurationManager.shared.forceRefresh() + ConfigurationManager.shared.forceRefresh(isDebug: true) if let configurationUrl { os_log("New configuration URL set to \(configurationUrl.absoluteString)", type: .info) } else { diff --git a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift index f4d0faec88..1b1aad17d4 100644 --- a/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift +++ b/DuckDuckGo/NavigationBar/View/MoreOptionsMenu.swift @@ -149,11 +149,15 @@ final class MoreOptionsMenu: NSMenu { #if DBP @objc func openDataBrokerProtection(_ sender: NSMenuItem) { - if !DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist && DataBrokerProtectionWaitlistViewControllerPresenter.shouldPresentWaitlist() { + #if SUBSCRIPTION + actionDelegate?.optionsButtonMenuRequestedDataBrokerProtection(self) + #else + if !DefaultDataBrokerProtectionFeatureVisibility.bypassWaitlist && DataBrokerProtectionWaitlistViewControllerPresenter.shouldPresentWaitlist() { DataBrokerProtectionWaitlistViewControllerPresenter.show() } else { actionDelegate?.optionsButtonMenuRequestedDataBrokerProtection(self) } + #endif } #endif // DBP diff --git a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift index dec1e1cf46..a7dc7255d6 100644 --- a/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift +++ b/DuckDuckGo/NetworkProtection/AppTargets/BothAppTargets/VPNLocation/VPNLocationView.swift @@ -61,7 +61,7 @@ struct VPNLocationView: View { @ViewBuilder private func nearest(isSelected: Bool) -> some View { - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.vpnLocationRecommendedSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) @@ -98,7 +98,7 @@ struct VPNLocationView: View { EmptyView() .listRowBackground(Color.clear) case .loaded(let countryItems): - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.vpnLocationCustomSectionTitle) .font(.system(size: 15)) .foregroundColor(.primary) diff --git a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift index 87c5614ecb..6bc1dfa909 100644 --- a/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift +++ b/DuckDuckGo/NetworkProtection/NetworkExtensionTargets/NetworkExtensionTargets/MacPacketTunnelProvider.swift @@ -379,8 +379,17 @@ final class MacPacketTunnelProvider: PacketTunnelProvider { dryRun = false #endif + let source: String + +#if NETP_SYSTEM_EXTENSION + source = "vpnSystemExtension" +#else + source = "vpnAppExtension" +#endif + PixelKit.setUp(dryRun: dryRun, appVersion: AppVersion.shared.versionNumber, + source: source, defaultHeaders: defaultHeaders, log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in diff --git a/DuckDuckGo/Preferences/Model/PreferencesSection.swift b/DuckDuckGo/Preferences/Model/PreferencesSection.swift index 2c71c401a7..4e32a5af5f 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSection.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSection.swift @@ -24,7 +24,7 @@ struct PreferencesSection: Hashable, Identifiable { let panes: [PreferencePaneIdentifier] @MainActor - static func defaultSections(includingDuckPlayer: Bool, includingVPN: Bool) -> [PreferencesSection] { + static func defaultSections(includingDuckPlayer: Bool, includingSync: Bool, includingVPN: Bool) -> [PreferencesSection] { let regularPanes: [PreferencePaneIdentifier] = { #if SUBSCRIPTION var panes: [PreferencePaneIdentifier] = [.privacy, .subscription, .general, .appearance, .autofill, .downloads] @@ -37,7 +37,7 @@ struct PreferencesSection: Hashable, Identifiable { #else var panes: [PreferencePaneIdentifier] = [.general, .appearance, .privacy, .autofill, .downloads] - if NSApp.delegateTyped.internalUserDecider.isInternalUser { + if includingSync { panes.insert(.sync, at: 1) } #endif @@ -92,6 +92,7 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { self.init(rawValue: path) } + @MainActor var displayName: String { switch self { case .general: @@ -99,7 +100,9 @@ enum PreferencePaneIdentifier: String, Equatable, Hashable, Identifiable { case .sync: let isSyncBookmarksPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncBookmarksPaused.rawValue) let isSyncCredentialsPaused = UserDefaults.standard.bool(forKey: UserDefaultsWrapper.Key.syncCredentialsPaused.rawValue) - if isSyncBookmarksPaused || isSyncCredentialsPaused { + let syncService = NSApp.delegateTyped.syncService + let isDataSyncingDisabled = syncService?.featureFlags.contains(.dataSyncing) == false && syncService?.authState == .active + if isSyncBookmarksPaused || isSyncCredentialsPaused || isDataSyncingDisabled { return UserText.sync + " ⚠️" } return UserText.sync diff --git a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift index 363378641a..11e81dd11c 100644 --- a/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift +++ b/DuckDuckGo/Preferences/Model/PreferencesSidebarModel.swift @@ -16,9 +16,10 @@ // limitations under the License. // -import SwiftUI import BrowserServicesKit import Combine +import DDGSync +import SwiftUI final class PreferencesSidebarModel: ObservableObject { @@ -33,7 +34,8 @@ final class PreferencesSidebarModel: ObservableObject { init( loadSections: @escaping () -> [PreferencesSection], tabSwitcherTabs: [Tab.TabContent], - privacyConfigurationManager: PrivacyConfigurationManaging + privacyConfigurationManager: PrivacyConfigurationManaging, + syncService: DDGSyncing ) { self.loadSections = loadSections self.tabSwitcherTabs = tabSwitcherTabs @@ -41,12 +43,18 @@ final class PreferencesSidebarModel: ObservableObject { resetTabSelectionIfNeeded() refreshSections() - privacyConfigurationManager.updatesPublisher + let duckPlayerFeatureFlagDidChange = privacyConfigurationManager.updatesPublisher .map { [weak privacyConfigurationManager] in privacyConfigurationManager?.privacyConfig.isEnabled(featureKey: .duckPlayer) == true } .removeDuplicates() .asVoid() + + let syncFeatureFlagsDidChange = syncService.featureFlagsPublisher.map { $0.contains(.userInterface) } + .removeDuplicates() + .asVoid() + + Publishers.Merge(duckPlayerFeatureFlagDidChange, syncFeatureFlagsDidChange) .receive(on: DispatchQueue.main) .sink { [weak self] in self?.refreshSections() @@ -62,6 +70,7 @@ final class PreferencesSidebarModel: ObservableObject { convenience init( tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes, privacyConfigurationManager: PrivacyConfigurationManaging = ContentBlocking.shared.privacyConfigurationManager, + syncService: DDGSyncing, includeDuckPlayer: Bool ) { let loadSections = { @@ -71,12 +80,17 @@ final class PreferencesSidebarModel: ObservableObject { let includingVPN = false #endif - return PreferencesSection.defaultSections(includingDuckPlayer: includeDuckPlayer, includingVPN: includingVPN) + return PreferencesSection.defaultSections( + includingDuckPlayer: includeDuckPlayer, + includingSync: syncService.featureFlags.contains(.userInterface), + includingVPN: includingVPN + ) } self.init(loadSections: loadSections, tabSwitcherTabs: tabSwitcherTabs, - privacyConfigurationManager: privacyConfigurationManager) + privacyConfigurationManager: privacyConfigurationManager, + syncService: syncService) } // MARK: - Setup diff --git a/DuckDuckGo/Preferences/Model/SyncPreferences.swift b/DuckDuckGo/Preferences/Model/SyncPreferences.swift index 60933937e5..e1f62308ba 100644 --- a/DuckDuckGo/Preferences/Model/SyncPreferences.swift +++ b/DuckDuckGo/Preferences/Model/SyncPreferences.swift @@ -89,14 +89,38 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { private var isScreenLocked: Bool = false private var recoveryKey: SyncCode.RecoveryKey? + @Published var syncFeatureFlags: SyncFeatureFlags { + didSet { + updateSyncFeatureFlags(syncFeatureFlags) + } + } + + @Published var isDataSyncingAvailable: Bool = true + @Published var isConnectingDevicesAvailable: Bool = true + @Published var isAccountCreationAvailable: Bool = true + @Published var isAccountRecoveryAvailable: Bool = true + + private func updateSyncFeatureFlags(_ syncFeatureFlags: SyncFeatureFlags) { + isDataSyncingAvailable = syncFeatureFlags.contains(.dataSyncing) + isConnectingDevicesAvailable = syncFeatureFlags.contains(.connectFlows) + isAccountCreationAvailable = syncFeatureFlags.contains(.accountCreation) + isAccountRecoveryAvailable = syncFeatureFlags.contains(.accountRecovery) + } + var recoveryCode: String? { syncService.account?.recoveryCode } - init(syncService: DDGSyncing, syncBookmarksAdapter: SyncBookmarksAdapter, appearancePreferences: AppearancePreferences = .shared, managementDialogModel: ManagementDialogModel = ManagementDialogModel()) { + init( + syncService: DDGSyncing, + syncBookmarksAdapter: SyncBookmarksAdapter, + appearancePreferences: AppearancePreferences = .shared, + managementDialogModel: ManagementDialogModel = ManagementDialogModel() + ) { self.syncService = syncService self.syncBookmarksAdapter = syncBookmarksAdapter self.appearancePreferences = appearancePreferences + self.syncFeatureFlags = syncService.featureFlags self.isFaviconsFetchingEnabled = syncBookmarksAdapter.isFaviconsFetchingEnabled self.isUnifiedFavoritesEnabled = appearancePreferences.favoritesDisplayMode.isDisplayUnified @@ -106,11 +130,19 @@ final class SyncPreferences: ObservableObject, SyncUI.ManagementViewModel { self.managementDialogModel = managementDialogModel self.managementDialogModel.delegate = self + updateSyncFeatureFlags(self.syncFeatureFlags) setUpObservables() setUpSyncOptionsObservables(apperancePreferences: appearancePreferences) } private func setUpObservables() { + syncService.featureFlagsPublisher + .dropFirst() + .removeDuplicates() + .receive(on: DispatchQueue.main) + .assign(to: \.syncFeatureFlags, onWeaklyHeld: self) + .store(in: &cancellables) + syncService.authStatePublisher .removeDuplicates() .asVoid() diff --git a/DuckDuckGo/Preferences/View/PreferencesViewController.swift b/DuckDuckGo/Preferences/View/PreferencesViewController.swift index 0289ac6afc..0a9e3823d6 100644 --- a/DuckDuckGo/Preferences/View/PreferencesViewController.swift +++ b/DuckDuckGo/Preferences/View/PreferencesViewController.swift @@ -19,6 +19,7 @@ import AppKit import SwiftUI import Combine +import DDGSync #if NETWORK_PROTECTION import NetworkProtection @@ -28,12 +29,21 @@ final class PreferencesViewController: NSViewController { weak var delegate: BrowserTabSelectionDelegate? - let model = PreferencesSidebarModel(includeDuckPlayer: DuckPlayer.shared.isAvailable) + let model: PreferencesSidebarModel private var selectedTabIndexCancellable: AnyCancellable? private var selectedPreferencePaneCancellable: AnyCancellable? private var bitwardenManager: BWManagement = BWManager.shared + init(syncService: DDGSyncing, duckPlayer: DuckPlayer = DuckPlayer.shared) { + model = PreferencesSidebarModel(syncService: syncService, includeDuckPlayer: duckPlayer.isAvailable) + super.init(nibName: nil, bundle: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func loadView() { view = NSView() } diff --git a/DuckDuckGo/Statistics/PixelEvent.swift b/DuckDuckGo/Statistics/PixelEvent.swift index 9658237d00..795ea7ebb3 100644 --- a/DuckDuckGo/Statistics/PixelEvent.swift +++ b/DuckDuckGo/Statistics/PixelEvent.swift @@ -200,6 +200,9 @@ extension Pixel { case dataBrokerProtectionRemoteMessageDismissed(messageID: String) case dataBrokerProtectionRemoteMessageOpened(messageID: String) + // DataBrokerProtection Other + case dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn + // 28-day Home Button case homeButtonHidden case homeButtonLeft @@ -313,7 +316,6 @@ extension Pixel { case missingParent case bookmarksSaveFailed case bookmarksSaveFailedOnImport - case orphanedBookmarksPresent case bookmarksCouldNotLoadDatabase case bookmarksCouldNotPrepareDatabase @@ -545,6 +547,9 @@ extension Pixel.Event { return "m_mac_dbp_imp_terms" case .dataBrokerProtectionWaitlistTermsAndConditionsAccepted: return "m_mac_dbp_ev_terms_accepted" + case .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn: + return "m_mac_dbp_error_when_fetching_subscription_auth_token_after_sign_in" + case .dataBrokerProtectionRemoteMessageDisplayed(let messageID): return "m_mac_dbp_remote_message_displayed_\(messageID)" case .dataBrokerProtectionRemoteMessageDismissed(let messageID): @@ -765,7 +770,6 @@ extension Pixel.Event.Debug { case .missingParent: return "bookmark_missing_parent" case .bookmarksSaveFailed: return "bookmarks_save_failed" case .bookmarksSaveFailedOnImport: return "bookmarks_save_failed_on_import" - case .orphanedBookmarksPresent: return "bookmarks_orphans_present" case .bookmarksCouldNotLoadDatabase: return "bookmarks_could_not_load_database" case .bookmarksCouldNotPrepareDatabase: return "bookmarks_could_not_prepare_database" diff --git a/DuckDuckGo/Statistics/PixelParameters.swift b/DuckDuckGo/Statistics/PixelParameters.swift index fb825e5c3f..299b6f53d4 100644 --- a/DuckDuckGo/Statistics/PixelParameters.swift +++ b/DuckDuckGo/Statistics/PixelParameters.swift @@ -149,6 +149,7 @@ extension Pixel.Event { .dataBrokerProtectionWaitlistCardUITapped, .dataBrokerProtectionWaitlistTermsAndConditionsDisplayed, .dataBrokerProtectionWaitlistTermsAndConditionsAccepted, + .dataBrokerProtectionErrorWhenFetchingSubscriptionAuthTokenAfterSignIn, .homeButtonLeft, .homeButtonRight, .homeButtonHidden, diff --git a/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift b/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift index 2f28741993..3cd482ced8 100644 --- a/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift +++ b/DuckDuckGo/Sync/FaviconsFetcherOnboarding.swift @@ -68,7 +68,8 @@ final class FaviconsFetcherOnboarding { } private var shouldPresentOnboarding: Bool { - !didPresentFaviconsFetchingOnboarding + syncService.featureFlags.contains(.userInterface) + && !didPresentFaviconsFetchingOnboarding && !syncBookmarksAdapter.isFaviconsFetchingEnabled && syncBookmarksAdapter.isEligibleForFaviconsFetcherOnboarding } diff --git a/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift b/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift index ba60396610..75fdbbea30 100644 --- a/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift +++ b/DuckDuckGo/Tab/Navigation/ExternalAppSchemeHandler.swift @@ -53,8 +53,8 @@ final class ExternalAppSchemeHandler { extension ExternalAppSchemeHandler: NavigationResponder { - @MainActor - func decidePolicy(for navigationAction: NavigationAction, preferences: inout NavigationPreferences) async -> NavigationActionPolicy? { + // swiftlint:disable:next function_body_length + @MainActor func decidePolicy(for navigationAction: NavigationAction, preferences: inout NavigationPreferences) async -> NavigationActionPolicy? { let externalUrl = navigationAction.url // only proceed with non-external-scheme navigations guard externalUrl.isExternalSchemeLink, let scheme = externalUrl.scheme else { @@ -114,19 +114,18 @@ extension ExternalAppSchemeHandler: NavigationResponder { } let permissionType = PermissionType.externalScheme(scheme: scheme) - // use domain from the url for user-entered app schemes, otherwise use current website domain - let domain = navigationAction.isUserEnteredUrl ? navigationAction.url.host ?? "" : navigationAction.sourceFrame.securityOrigin.host - permissionModel.permissions([permissionType], requestedForDomain: domain, url: externalUrl) { [workspace, weak self] isGranted in + // Check for cross-origin redirects first, then use domain from the url for user-entered app schemes, then use current website domain + let redirectDomain = navigationAction.redirectHistory?.reversed().first(where: { $0.url.host != navigationAction.url.host })?.url.host + let domain = redirectDomain ?? (navigationAction.isUserEnteredUrl ? navigationAction.url.host ?? "" : navigationAction.sourceFrame.securityOrigin.host) + permissionModel.permissions([permissionType], requestedForDomain: domain, url: externalUrl) { [workspace] isGranted in if isGranted { workspace.open(externalUrl) - // if "Always allow" is set and this is the only navigation in the tab: close the tab - if self?.shouldCloseTabOnExternalAppOpen == true, let webView = navigationAction.targetFrame?.webView { + if self.shouldCloseTabOnExternalAppOpen == true, let webView = navigationAction.targetFrame?.webView { webView.close() } } } - return .cancel } diff --git a/DuckDuckGo/Tab/View/BrowserTabViewController.swift b/DuckDuckGo/Tab/View/BrowserTabViewController.swift index fc216e21c3..1192e49e25 100644 --- a/DuckDuckGo/Tab/View/BrowserTabViewController.swift +++ b/DuckDuckGo/Tab/View/BrowserTabViewController.swift @@ -219,6 +219,10 @@ final class BrowserTabViewController: NSViewController { tabCollectionViewModel.tabCollection.$tabs .sink(receiveValue: setDelegate()) .store(in: &cancellables) + + tabCollectionViewModel.tabCollection.$tabs + .sink(receiveValue: removeDataBrokerViewIfNecessary()) + .store(in: &cancellables) } private func subscribeToPinnedTabs() { @@ -226,6 +230,19 @@ final class BrowserTabViewController: NSViewController { .sink(receiveValue: setDelegate()) } + private func removeDataBrokerViewIfNecessary() -> ([Tab]) -> Void { + { [weak self] (tabs: [Tab]) in + guard let self else { return } +#if DBP + if let dataBrokerProtectionHomeViewController, + !tabs.contains(where: { $0.content == .dataBrokerProtection }) { + dataBrokerProtectionHomeViewController.removeCompletely() + self.dataBrokerProtectionHomeViewController = nil + } +#endif + } + } + private func setDelegate() -> ([Tab]) -> Void { { [weak self] (tabs: [Tab]) in guard let self else { return } @@ -431,7 +448,6 @@ final class BrowserTabViewController: NSViewController { bookmarksViewController?.removeCompletely() #if DBP dataBrokerProtectionHomeViewController?.removeCompletely() - dataBrokerProtectionHomeViewController = nil #endif if includingWebView { self.removeWebViewFromHierarchy() @@ -530,7 +546,10 @@ final class BrowserTabViewController: NSViewController { var preferencesViewController: PreferencesViewController? private func preferencesViewControllerCreatingIfNeeded() -> PreferencesViewController { return preferencesViewController ?? { - let preferencesViewController = PreferencesViewController() + guard let syncService = NSApp.delegateTyped.syncService else { + fatalError("Sync service is nil") + } + let preferencesViewController = PreferencesViewController(syncService: syncService) preferencesViewController.delegate = self self.preferencesViewController = preferencesViewController return preferencesViewController diff --git a/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift b/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift index 87b6e010a6..b184a883df 100644 --- a/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift +++ b/DuckDuckGoDBPBackgroundAgent/DBPMocks.swift @@ -56,21 +56,23 @@ final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { guard let privacyConfigurationData = try? PrivacyConfigurationData(data: data) else { fatalError("Could not retrieve privacy configuration data") } - let privacyConfig = privacyConfiguration(withData: privacyConfigurationData) + let privacyConfig = privacyConfiguration(withData: privacyConfigurationData, internalUserDecider: internalUserDecider) return privacyConfig } + var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) + func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { .downloaded } } -func privacyConfiguration(withData data: PrivacyConfigurationData) -> PrivacyConfiguration { +func privacyConfiguration(withData data: PrivacyConfigurationData, internalUserDecider: InternalUserDecider) -> PrivacyConfiguration { let domain = MockDomainsProtectionStore() return AppPrivacyConfiguration(data: data, identifier: UUID().uuidString, localProtection: domain, - internalUserDecider: DefaultInternalUserDecider(store: InternalUserDeciderStoreMock())) + internalUserDecider: internalUserDecider) } final class MockDomainsProtectionStore: DomainsProtectionStore { diff --git a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift index f2ac32192a..9c8daa0d36 100644 --- a/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift +++ b/DuckDuckGoDBPBackgroundAgent/DuckDuckGoDBPBackgroundAgentAppDelegate.swift @@ -41,6 +41,7 @@ final class DuckDuckGoDBPBackgroundAgentApplication: NSApplication { PixelKit.setUp(dryRun: dryRun, appVersion: AppVersion.shared.versionNumber, + source: nil, defaultHeaders: [:], log: .dbpBackgroundAgentPixel, defaults: .standard) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping (Bool, Error?) -> Void) in diff --git a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift index bc3af58b03..71f37ea3ba 100644 --- a/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift +++ b/DuckDuckGoVPN/DuckDuckGoVPNAppDelegate.swift @@ -173,7 +173,12 @@ final class DuckDuckGoVPNAppDelegate: NSObject, NSApplicationDelegate { dryRun = false #endif - PixelKit.setUp(dryRun: dryRun, appVersion: AppVersion.shared.versionNumber, defaultHeaders: [:], log: .networkProtectionPixel, defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in + PixelKit.setUp(dryRun: dryRun, + appVersion: AppVersion.shared.versionNumber, + source: "vpnAgent", + defaultHeaders: [:], + log: .networkProtectionPixel, + defaults: .netP) { (pixelName: String, headers: [String: String], parameters: [String: String], _, _, onComplete: @escaping PixelKit.CompletionBlock) in let url = URL.pixelUrl(forPixelNamed: pixelName) let apiHeaders = APIRequest.Headers(additionalHeaders: headers) // workaround - Pixel class should really handle APIRequest.Headers by itself diff --git a/LocalPackages/Account/Package.swift b/LocalPackages/Account/Package.swift index 05b9494e5f..49f48da084 100644 --- a/LocalPackages/Account/Package.swift +++ b/LocalPackages/Account/Package.swift @@ -12,7 +12,7 @@ let package = Package( targets: ["Account"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), .package(path: "../Purchase") ], targets: [ diff --git a/LocalPackages/Account/Sources/Account/AccountManager.swift b/LocalPackages/Account/Sources/Account/AccountManager.swift index b03c409908..8c3fe49ba3 100644 --- a/LocalPackages/Account/Sources/Account/AccountManager.swift +++ b/LocalPackages/Account/Sources/Account/AccountManager.swift @@ -29,7 +29,13 @@ public protocol AccountManagerKeychainAccessDelegate: AnyObject { func accountManagerKeychainAccessFailed(accessType: AccountKeychainAccessType, error: AccountKeychainAccessError) } -public class AccountManager { +public protocol AccountManaging { + + var token: String? { get } + +} + +public class AccountManager: AccountManaging { private let storage: AccountStorage public weak var delegate: AccountManagerKeychainAccessDelegate? diff --git a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift index 7548491a80..83d9cda1fc 100644 --- a/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift +++ b/LocalPackages/BuildToolPlugins/Plugins/InputFilesChecker/InputFilesChecker.swift @@ -44,7 +44,8 @@ let nonSandboxedExtraInputFiles: Set = [ .init("VPNMetadataCollector.swift", .source), .init("VPNFeedbackCategory.swift", .source), .init("VPNFeedbackSender.swift", .source), - .init("DuckDuckGoDBPBackgroundAgent.app", .unknown) + .init("DuckDuckGoDBPBackgroundAgent.app", .unknown), + .init("DataBrokerProtectionSubscriptionEventHandler.swift", .source) ] /** diff --git a/LocalPackages/DataBrokerProtection/Package.swift b/LocalPackages/DataBrokerProtection/Package.swift index ef12d3e7c6..435391c9df 100644 --- a/LocalPackages/DataBrokerProtection/Package.swift +++ b/LocalPackages/DataBrokerProtection/Package.swift @@ -29,7 +29,7 @@ let package = Package( targets: ["DataBrokerProtection"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), .package(path: "../PixelKit"), .package(path: "../SwiftUIExtensions"), .package(path: "../XPCHelper") diff --git a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift index 5177325d43..d65b1653b2 100644 --- a/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift +++ b/LocalPackages/DataBrokerProtection/Sources/DataBrokerProtection/CCF/DataBrokerProtectionUtils.swift @@ -163,6 +163,7 @@ private class PrivacyConfigurationDataBrokerProtectionConfigOverride: PrivacyCon var base: Data var updatesPublisher: AnyPublisher<(), Never> var privacyConfig: PrivacyConfiguration + var internalUserDecider: InternalUserDecider var currentConfig: Data { return updateConfigWithBrokerProtection() @@ -176,6 +177,7 @@ private class PrivacyConfigurationDataBrokerProtectionConfigOverride: PrivacyCon base = manager.currentConfig updatesPublisher = manager.updatesPublisher privacyConfig = manager.privacyConfig + internalUserDecider = manager.internalUserDecider } private func updateConfigWithBrokerProtection() -> Data { diff --git a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift index d33f4b6e5b..b9699bd4e2 100644 --- a/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift +++ b/LocalPackages/DataBrokerProtection/Tests/DataBrokerProtectionTests/Mocks.swift @@ -58,6 +58,10 @@ extension DataBrokerScheduleConfig { } } +final class InternalUserDeciderStoreMock: InternalUserStoring { + var isInternalUser: Bool = false +} + final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { var currentConfig: Data = Data() @@ -65,6 +69,8 @@ final class PrivacyConfigurationManagingMock: PrivacyConfigurationManaging { var privacyConfig: BrowserServicesKit.PrivacyConfiguration = PrivacyConfigurationMock() + var internalUserDecider: InternalUserDecider = DefaultInternalUserDecider(store: InternalUserDeciderStoreMock()) + func reload(etag: String?, data: Data?) -> PrivacyConfigurationManager.ReloadResult { .downloaded } diff --git a/LocalPackages/LoginItems/Package.swift b/LocalPackages/LoginItems/Package.swift index 2920672864..53da2ff762 100644 --- a/LocalPackages/LoginItems/Package.swift +++ b/LocalPackages/LoginItems/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/NetworkProtectionMac/Package.swift b/LocalPackages/NetworkProtectionMac/Package.swift index 148359e65b..cd031f9386 100644 --- a/LocalPackages/NetworkProtectionMac/Package.swift +++ b/LocalPackages/NetworkProtectionMac/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "NetworkProtectionUI", targets: ["NetworkProtectionUI"]) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), .package(path: "../XPCHelper"), .package(path: "../SwiftUIExtensions") ], diff --git a/LocalPackages/PixelKit/Package.swift b/LocalPackages/PixelKit/Package.swift index e629606c8a..f4230052ea 100644 --- a/LocalPackages/PixelKit/Package.swift +++ b/LocalPackages/PixelKit/Package.swift @@ -20,7 +20,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift index 9673f0dae0..70bf4aae8a 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit+Parameters.swift @@ -24,6 +24,7 @@ public extension PixelKit { public static let duration = "duration" public static let test = "test" public static let appVersion = "appVersion" + public static let pixelSource = "pixelSource" public static let osMajorVersion = "osMajorVersion" public static let errorCode = "e" diff --git a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift index c8d165ad5f..b13aea7b17 100644 --- a/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift +++ b/LocalPackages/PixelKit/Sources/PixelKit/PixelKit.swift @@ -87,10 +87,26 @@ public final class PixelKit { private let log: OSLog private let fireRequest: FireRequest - /// `dryRun`: if `true`, simulate requests and "send" them at an accelerated rate (once every 2 minutes instead of once a day) - /// `fireRequest`: this is not triggered when `dryRun` is `true` - public static func setUp(dryRun: Bool = false, appVersion: String, defaultHeaders: [String: String], log: OSLog, defaults: UserDefaults, fireRequest: @escaping FireRequest) { - shared = PixelKit(dryRun: dryRun, appVersion: appVersion, defaultHeaders: defaultHeaders, log: log, defaults: defaults, fireRequest: fireRequest) + /// Sets up PixelKit for the entire app. + /// + /// - Parameters: + /// - `dryRun`: if `true`, simulate requests and "send" them at an accelerated rate (once every 2 minutes instead of once a day) + /// - `source`: if set, adds a `pixelSource` parameter to the pixel call; this can be used to specify which target is sending the pixel + /// - `fireRequest`: this is not triggered when `dryRun` is `true` + public static func setUp(dryRun: Bool = false, + appVersion: String, + source: String? = nil, + defaultHeaders: [String: String], + log: OSLog, + defaults: UserDefaults, + fireRequest: @escaping FireRequest) { + shared = PixelKit(dryRun: dryRun, + appVersion: appVersion, + source: source, + defaultHeaders: defaultHeaders, + log: log, + defaults: defaults, + fireRequest: fireRequest) } static func tearDown() { @@ -98,10 +114,12 @@ public final class PixelKit { } private var dryRun: Bool + private let source: String? private let pixelCalendar: Calendar init(dryRun: Bool, appVersion: String, + source: String? = nil, defaultHeaders: [String: String], log: OSLog, dailyPixelCalendar: Calendar? = nil, @@ -111,6 +129,7 @@ public final class PixelKit { self.dryRun = dryRun self.appVersion = appVersion + self.source = source self.defaultHeaders = defaultHeaders self.log = log self.pixelCalendar = dailyPixelCalendar ?? Self.defaultDailyPixelCalendar @@ -119,6 +138,7 @@ public final class PixelKit { self.fireRequest = fireRequest } + // swiftlint:disable:next cyclomatic_complexity private func fire(pixelNamed pixelName: String, frequency: Frequency, withHeaders headers: [String: String]?, @@ -134,6 +154,10 @@ public final class PixelKit { newParams[Parameters.appVersion] = appVersion } + if let source { + newParams[Parameters.pixelSource] = source + } + if let error { newParams.appendErrorPixelParams(error: error) } diff --git a/LocalPackages/Purchase/Package.swift b/LocalPackages/Purchase/Package.swift index 8f773ac9a6..a61123f147 100644 --- a/LocalPackages/Purchase/Package.swift +++ b/LocalPackages/Purchase/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/Subscription/Package.swift b/LocalPackages/Subscription/Package.swift index 08009324ff..02148554e7 100644 --- a/LocalPackages/Subscription/Package.swift +++ b/LocalPackages/Subscription/Package.swift @@ -15,7 +15,7 @@ let package = Package( .package(path: "../Account"), .package(path: "../Purchase"), .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Package.swift b/LocalPackages/SwiftUIExtensions/Package.swift index 233c7d2eba..7ca05a42ec 100644 --- a/LocalPackages/SwiftUIExtensions/Package.swift +++ b/LocalPackages/SwiftUIExtensions/Package.swift @@ -13,7 +13,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift index 928b489ddc..0fadaa2d19 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/PreferencePaneSection.swift @@ -24,9 +24,9 @@ public struct PreferencePaneSection: View where Content: View { public let verticalPadding: CGFloat @ViewBuilder public let content: () -> Content - public init(spacing: CGFloat = 12, vericalPadding: CGFloat = 20, @ViewBuilder content: @escaping () -> Content) { + public init(spacing: CGFloat = 12, verticalPadding: CGFloat = 20, @ViewBuilder content: @escaping () -> Content) { self.spacing = spacing - self.verticalPadding = vericalPadding + self.verticalPadding = verticalPadding self.content = content } diff --git a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift index 329cb46b85..1e73626837 100644 --- a/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift +++ b/LocalPackages/SwiftUIExtensions/Sources/SwiftUIExtensions/TextButton.swift @@ -21,16 +21,19 @@ import SwiftUI public struct TextButton: View { public let title: String + public let fontWeight: Font.Weight public let action: () -> Void - public init(_ title: String, action: @escaping () -> Void) { + public init(_ title: String, weight: Font.Weight = .regular, action: @escaping () -> Void) { self.title = title + self.fontWeight = weight self.action = action } public var body: some View { Button(action: action) { Text(title) + .fontWeight(fontWeight) .foregroundColor(Color("LinkBlueColor")) } .buttonStyle(.plain) diff --git a/LocalPackages/SyncUI/Package.swift b/LocalPackages/SyncUI/Package.swift index 5d6fa9f888..6f485526ca 100644 --- a/LocalPackages/SyncUI/Package.swift +++ b/LocalPackages/SyncUI/Package.swift @@ -13,7 +13,7 @@ let package = Package( ], dependencies: [ .package(path: "../SwiftUIExtensions"), - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ .target( diff --git a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift index 89cb583ab2..4e1d1de021 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/ViewModels/ManagementViewModel.swift @@ -20,6 +20,11 @@ import Foundation public protocol ManagementViewModel: ObservableObject { + var isDataSyncingAvailable: Bool { get } + var isConnectingDevicesAvailable: Bool { get } + var isAccountCreationAvailable: Bool { get } + var isAccountRecoveryAvailable: Bool { get } + var isSyncEnabled: Bool { get } var isCreatingAccount: Bool { get } var shouldShowErrorMessage: Bool { get set } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift index 2f17ab011d..53ffb0513e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncEnabledView.swift @@ -25,6 +25,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { var body: some View { // Errors VStack(alignment: .leading, spacing: 16) { + syncUnavailableView() if model.isSyncBookmarksPaused { syncPaused(for: .bookmarks) } @@ -34,14 +35,14 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Sync Enabled - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { SyncStatusView() .environmentObject(model) .frame(width: 513, alignment: .topLeading) } // Synced Devices - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.syncedDevices) .font(Const.Fonts.preferencePaneSectionHeader) .padding(.horizontal, 16) @@ -51,7 +52,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Options - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Text(UserText.optionsSectionTitle) .font(Const.Fonts.preferencePaneSectionHeader) .padding(.horizontal, 16) @@ -94,7 +95,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Recovery - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { VStack(alignment: .leading, spacing: 6) { Text(UserText.recovery) .font(Const.Fonts.preferencePaneSectionHeader) @@ -112,7 +113,7 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { } // Turn Off and Delate Data - PreferencePaneSection(vericalPadding: 12) { + PreferencePaneSection(verticalPadding: 12) { Button(UserText.turnOffAndDeleteServerData) { model.presentDeleteAccount() } @@ -138,28 +139,23 @@ struct SyncEnabledView: View where ViewModel: ManagementViewModel { return UserText.credentialsLimitExceededAction } } - PreferencePaneSection(vericalPadding: 16) { - HStack(alignment: .top, spacing: 8) { - Text("⚠️") - VStack(alignment: .leading, spacing: 8) { - Text(UserText.syncLimitExceededTitle) - .bold() - Text(description) - Button(actionTitle) { - switch itemType { - case .bookmarks: - model.manageBookmarks() - case .credentials: - model.manageLogins() - } - } - .padding(.top, 8) - } + SyncWarningMessage(title: UserText.syncLimitExceededTitle, message: description, buttonTitle: actionTitle) { + switch itemType { + case .bookmarks: + model.manageBookmarks() + case .credentials: + model.manageLogins() } - .padding(.horizontal, 16) } - .frame(width: 512, alignment: .leading) - .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } + + @ViewBuilder + fileprivate func syncUnavailableView() -> some View { + if model.isDataSyncingAvailable { + EmptyView() + } else { + SyncWarningMessage(title: UserText.syncPausedTitle, message: UserText.syncUnavailableMessage) + } } enum LimitedItemType { diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift index 471fb6a546..133f1b4b2a 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncSetupView.swift @@ -22,34 +22,10 @@ import SwiftUIExtensions struct SyncSetupView: View where ViewModel: ManagementViewModel { @EnvironmentObject var model: ViewModel - fileprivate func syncWithAnotherDeviceView() -> some View { - return VStack(alignment: .center, spacing: 16) { - Image("Sync-Pair-96") - VStack(alignment: .center, spacing: 8) { - SyncUIViews.TextHeader(text: UserText.beginSyncTitle) - SyncUIViews.TextDetailSecondary(text: UserText.beginSyncDescription) - } - .padding(.bottom, 16) - ZStack { - RoundedRectangle(cornerRadius: 8) - .fill(Color("LinkBlueColor")) - .frame(width: 220, height: 32) - Text(UserText.beginSyncButton) - .foregroundColor(.white) - .bold() - } - .onTapGesture { - model.syncWithAnotherDevicePressed() - } - } - .frame(width: 512, height: 254) - .roundedBorder() - .padding(.top, 20) - } - var body: some View { VStack(alignment: .leading, spacing: 24) { VStack(spacing: 8) { + syncUnavailableView() syncWithAnotherDeviceView() SyncUIViews.TextDetailSecondary(text: UserText.beginSyncFooter) .padding(.bottom, 24) @@ -59,16 +35,64 @@ struct SyncSetupView: View where ViewModel: ManagementViewModel { VStack(alignment: .leading, spacing: 12) { SyncUIViews.TextHeader2(text: UserText.otherOptionsSectionTitle) VStack(alignment: .leading, spacing: 8) { - SyncUIViews.TextLink(text: UserText.syncThisDeviceLink) - .onTapGesture { - model.syncWithServerPressed() - } - SyncUIViews.TextLink(text: UserText.recoverDataLink) - .onTapGesture { - model.recoverDataPressed() - } + TextButton(UserText.syncThisDeviceLink, weight: .semibold, action: model.syncWithServerPressed) + .disabled(!model.isAccountCreationAvailable) + TextButton(UserText.recoverDataLink, weight: .semibold, action: model.recoverDataPressed) + .disabled(!model.isAccountRecoveryAvailable) } } } } + + fileprivate func syncWithAnotherDeviceView() -> some View { + VStack(alignment: .center, spacing: 16) { + Image("Sync-Pair-96") + VStack(alignment: .center, spacing: 8) { + SyncUIViews.TextHeader(text: UserText.beginSyncTitle) + SyncUIViews.TextDetailSecondary(text: UserText.beginSyncDescription) + } + .padding(.bottom, 16) + Button(UserText.beginSyncButton, action: model.syncWithAnotherDevicePressed) + .buttonStyle(SyncWithAnotherDeviceButtonStyle(enabled: model.isConnectingDevicesAvailable)) + .disabled(!model.isConnectingDevicesAvailable) + } + .frame(width: 512, height: 254) + .roundedBorder() + .padding(.top, 20) + } + + @ViewBuilder + fileprivate func syncUnavailableView() -> some View { + if !model.isDataSyncingAvailable || !model.isConnectingDevicesAvailable || !model.isAccountCreationAvailable { + SyncWarningMessage(title: UserText.syncUnavailableTitle, message: UserText.syncUnavailableMessage) + .padding(.top, 16) + } else { + EmptyView() + } + } +} + +private struct SyncWithAnotherDeviceButtonStyle: ButtonStyle { + + public let enabled: Bool + + public init(enabled: Bool) { + self.enabled = enabled + } + + public func makeBody(configuration: Self.Configuration) -> some View { + + let enabledBackgroundColor = configuration.isPressed ? Color(NSColor.controlAccentColor).opacity(0.5) : Color(NSColor.controlAccentColor) + let disabledBackgroundColor = Color.gray.opacity(0.1) + let labelColor = enabled ? Color.white : Color.primary.opacity(0.3) + + configuration.label + .lineLimit(1) + .font(.body.bold()) + .frame(width: 220, height: 32) + .background(enabled ? enabledBackgroundColor : disabledBackgroundColor) + .foregroundColor(labelColor) + .cornerRadius(8) + + } } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift new file mode 100644 index 0000000000..cf9a80ed0b --- /dev/null +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncWarningMessage.swift @@ -0,0 +1,53 @@ +// +// SyncWarningMessage.swift +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI +import SwiftUIExtensions + +struct SyncWarningMessage: View { + let title: String + let message: String + let buttonTitle: String? + let buttonAction: (() -> Void)? + + init(title: String, message: String, buttonTitle: String? = nil, buttonAction: (() -> Void)? = nil) { + self.title = title + self.message = message + self.buttonTitle = buttonTitle + self.buttonAction = buttonAction + } + + var body: some View { + PreferencePaneSection(verticalPadding: 16) { + HStack(alignment: .top, spacing: 8) { + Text("⚠️") + VStack(alignment: .leading, spacing: 8) { + Text(title).bold() + Text(message) + if let buttonTitle, let buttonAction { + Button(buttonTitle, action: buttonAction) + .padding(.top, 8) + } + } + } + .padding(.horizontal, 16) + } + .frame(width: 512, alignment: .leading) + .background(RoundedRectangle(cornerRadius: 8).foregroundColor(Color("AlertBubbleBackground"))) + } +} diff --git a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift index c026ad51ce..766f8596ca 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/Views/ManagementView/SyncedDevicesView.swift @@ -27,7 +27,7 @@ struct SyncedDevicesView: View where ViewModel: ManagementViewModel { let timer = Timer.publish(every: 3, on: .main, in: .common).autoconnect() var body: some View { - VStack { + VStack(alignment: .leading) { SyncedDevicesList(devices: model.devices, presentDeviceDetails: model.presentDeviceDetails, presentRemoveDevice: model.presentRemoveDevice) @@ -41,15 +41,14 @@ struct SyncedDevicesView: View where ViewModel: ManagementViewModel { .onDisappear { isVisible = false } - SyncPreferencesRow { - } centerContent: { - Button { - model.syncWithAnotherDevicePressed() - } label: { - Text("Sync with Another Device") - .frame(height: 24) - } + Button { + model.syncWithAnotherDevicePressed() + } label: { + Text(UserText.beginSyncButton) } + .disabled(!model.isConnectingDevicesAvailable) + .padding(.horizontal, 10) + .padding(.bottom, 8) } .roundedBorder() } diff --git a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift index 928aa1123f..57d66f925e 100644 --- a/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift +++ b/LocalPackages/SyncUI/Sources/SyncUI/internal/UserText.swift @@ -157,4 +157,10 @@ enum UserText { static let fetchFaviconsOnboardingTitle = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-title", value: "Download Missing Icons?", comment: "Title for fetch favicons onboarding dialog") static let fetchFaviconsOnboardingMessage = NSLocalizedString("prefrences.sync.fetch-favicons-onboarding-message", value: "Do you want this device to automatically download icons for any new bookmarks synced from your other devices? This will expose the download to your network any time a bookmark is synced.", comment: "Text for fetch favicons onboarding dialog") static let keepFaviconsUpdated = NSLocalizedString("prefrences.sync.keep-favicons-updated", value: "Keep Bookmarks Icons Updated", comment: "Title of the confirmation button for favicons fetching") + + // Sync Feature Flags + static let syncUnavailableTitle = NSLocalizedString("sync.warning.sync-unavailable", value: "Sync & Backup is Unavailable", comment: "Title of the warning message") + static let syncPausedTitle = NSLocalizedString("sync.warning.sync-paused", value: "Sync & Backup is Paused", comment: "Title of the warning message") + static let syncUnavailableMessage = NSLocalizedString("sync.warning.sync-unavailable-message", value: "Sorry, but Sync & Backup is currently unavailable. Please try again later.", comment: "Data syncing unavailable warning message") + static let syncUnavailableMessageUpgradeRequired = NSLocalizedString("sync.warning.data-syncing-disabled-upgrade-required", value: "Sorry, but Sync & Backup is no longer available in this app version. Please update DuckDuckGo to the latest version to continue.", comment: "Data syncing unavailable warning message") } diff --git a/LocalPackages/SystemExtensionManager/Package.swift b/LocalPackages/SystemExtensionManager/Package.swift index e9cb8b771e..722d4ae0a1 100644 --- a/LocalPackages/SystemExtensionManager/Package.swift +++ b/LocalPackages/SystemExtensionManager/Package.swift @@ -16,7 +16,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. diff --git a/LocalPackages/XPCHelper/Package.swift b/LocalPackages/XPCHelper/Package.swift index 5a182803f2..74bc880ffe 100644 --- a/LocalPackages/XPCHelper/Package.swift +++ b/LocalPackages/XPCHelper/Package.swift @@ -30,7 +30,7 @@ let package = Package( .library(name: "XPCHelper", targets: ["XPCHelper"]), ], dependencies: [ - .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "95.0.0"), + .package(url: "https://github.com/duckduckgo/BrowserServicesKit", exact: "98.0.1"), ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. diff --git a/UnitTests/DataImport/BookmarksHTMLReaderTests.swift b/UnitTests/DataImport/BookmarksHTMLReaderTests.swift index 543fe86c89..d83a094db3 100644 --- a/UnitTests/DataImport/BookmarksHTMLReaderTests.swift +++ b/UnitTests/DataImport/BookmarksHTMLReaderTests.swift @@ -16,87 +16,39 @@ // limitations under the License. // +import SnapshotTesting import XCTest @testable import DuckDuckGo_Privacy_Browser -class BookmarksHTMLReaderTests: XCTestCase { +final class BookmarksHTMLReaderTests: XCTestCase { - var reader: BookmarkHTMLReader! + let bookmarksHTMLReaderTestFilesURL = Bundle(for: BookmarksHTMLReaderTests.self) + .resourceURL! + .appendingPathComponent("DataImportResources/TestBookmarksData") - func bookmarksFileURL(_ name: String) -> URL { - let bundle = Bundle(for: ChromiumLoginReaderTests.self) - return bundle.resourceURL! - .appendingPathComponent("DataImportResources/TestBookmarksData") - .appendingPathComponent(name) - } - - func test_WhenParseChromeHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_chrome.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 12) - } - - func test_WhenParseSafariHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_safari.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 14) - } - - func test_WhenParseFirefoxHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_firefox.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 17) - } - - func test_WhenParseBraveHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_brave.html")) - let result = reader.readBookmarks() + @MainActor + func testBookmarksHTMLReaderSnapshot() throws { + let expectedToThrow: Set = [ + "bookmarks_invalid.html" + ] - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 12) - } + for fileName in try FileManager.default.contentsOfDirectory(atPath: bookmarksHTMLReaderTestFilesURL.path) { + let fileNameWithoutExtension = fileName.dropping(suffix: "html") + let fileURL = bookmarksHTMLReaderTestFilesURL.appendingPathComponent(fileName) + let reader = BookmarkHTMLReader(bookmarksFileURL: fileURL) + let result = reader.readBookmarks() - func test_WhenParseDDGAndroidHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_ddg_android.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 13) - } - - func test_WhenParseDDGiOSHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_ddg_ios.html")) - let result = reader.readBookmarks() - - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 8) - } - - func test_WhenParseDDGMacOSHtml_ThenImportSuccess() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_ddg_macos.html")) - let result = reader.readBookmarks() + if expectedToThrow.contains(fileName) { + XCTAssertThrowsError(try result.get(), fileNameWithoutExtension) + continue + } + guard case .success(let importResult) = result else { + XCTFail("unexpected failure in \(fileNameWithoutExtension): \(result)") + continue + } - let importedBookmarks = try XCTUnwrap(try? result.get()) - XCTAssertEqual(importedBookmarks.bookmarks.numberOfBookmarks, 13) + assertSnapshot(of: importResult.bookmarks, as: .json, named: fileNameWithoutExtension, testName: "snapshot") + } } - func test_WhenParseInvalidHtml_ThenImportFail() throws { - reader = BookmarkHTMLReader(bookmarksFileURL: bookmarksFileURL("bookmarks_invalid.html")) - let result = reader.readBookmarks() - - XCTAssertThrowsError(try result.get(), "", { error in - guard let error = error as? BookmarkHTMLReader.ImportError else { - XCTFail("Unexpected error type: \(String(reflecting: error))") - return - } - XCTAssertEqual(error.type, .parseXml) - XCTAssertEqual((error.underlyingError as NSError?)?.domain, XMLParser.errorDomain) - }) - } } diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html new file mode 100644 index 0000000000..a27442bcd2 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_chrome_dirty.html @@ -0,0 +1,10 @@ +

+

Bookmarks Bar

+

+

Bookmark 1 +
+
Bookmark 2 +
+

+

Bookmark 3 +

diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html new file mode 100644 index 0000000000..5edc0e29da --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_firefox_dirty.html @@ -0,0 +1,13 @@ +

+

Folder Name 1

+
+
Help and Tutorials +
Customize Firefox +
+ +

Folder Name B

+
+
Get Involved +
About Us +
+
diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html new file mode 100644 index 0000000000..d5a18a55b0 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_netscape_dirty.html @@ -0,0 +1,11 @@ + + +

+

Folder 1

+

+

Bookmark 1 +
+
Bookmark 2 +

+

Bookmark 3 +

diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html new file mode 100644 index 0000000000..9fc2f276b0 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_safari_tp_dirty.html @@ -0,0 +1,38 @@ + +] + +Bookmarks +

Bookmarks

+

Favorites

+

+

Apple +
Yahoo +
Google +
Wikipedia +
Facebook +
Twitter +
LinkedIn +
The Weather Channel +
Yelp +
+
catz - Google Search +
dogz - +Google Search +
ab +yrvalg - Google Search +

+

Bookmarks Menu

+

+

+

Tab Group Favorites

+

+

Tab Group Favorites

+

+

abyrvalg - Google Search +

+

diff --git a/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html new file mode 100644 index 0000000000..1739091c81 --- /dev/null +++ b/UnitTests/DataImport/DataImportResources/TestBookmarksData/bookmarks_vivaldi.html @@ -0,0 +1,46 @@ + + + +Bookmarks +

Bookmarks

+

+

Bookmarks

+

+

Speed Dial

+

+

Vivaldi Social +
Vivaldi Community +
Booking.com +
Amazon +
eBay +
AliExpress +
Eneba +
AccuWeather +
Vivaldia Games +

+

Bookmarks

+

+

Vivaldi

+

+

Vivaldi Features +
Vivaldi Webmail +
--- +
Vivaldi Help +

+

Booking.com +
Kayak +
AliExpress +
Amazon +
--- +
eBay +
Expedia +

+

Get Firefox for desktop — Mozilla (US) +
--- +

+

Trash

+

+

+

diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json new file mode 100644 index 0000000000..b9d3bdbff9 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_brave.json @@ -0,0 +1,150 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "Bookmarks bar", + "type" : "folder" + }, + "other" : { + "children" : [ + + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json new file mode 100644 index 0000000000..f854ba036d --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome.json @@ -0,0 +1,150 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + + ], + "name" : "Bookmarks Bar", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json new file mode 100644 index 0000000000..7a782dfb5f --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_chrome_dirty.json @@ -0,0 +1,31 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Bookmark 1", + "type" : "bookmark", + "url" : "https:\/\/example.com" + }, + { + "name" : "Bookmark 2", + "type" : "bookmark", + "url" : "https:\/\/example2.com" + } + ], + "name" : "Bookmarks Bar", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "name" : "Bookmark 3", + "type" : "bookmark", + "url" : "https:\/\/example3.com" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json new file mode 100644 index 0000000000..a07bec85d1 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_android.json @@ -0,0 +1,160 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "DuckDuckGo Bookmarks", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "name" : "Apple (United Kingdom)", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/uk\/" + } + ], + "name" : "DuckDuckGo Favorites", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json new file mode 100644 index 0000000000..1681f87cc5 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_ios.json @@ -0,0 +1,75 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + + ], + "name" : "", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com" + }, + { + "name" : "Hacker News", + "type" : "bookmark", + "url" : "https:\/\/news.ycombinator.com\/" + }, + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com" + }, + { + "name" : "The Linux Programming Interface: A Linux and UNIX System Programming Handbook: Amazon.co.uk: Michael Kerrisk: 9781593272203: Books", + "type" : "bookmark", + "url" : "https:\/\/www.amazon.co.uk\/Linux-Programming-Interface-System-Handbook\/dp\/1593272200" + }, + { + "children" : [ + { + "children" : [ + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com" + } + ], + "name" : "FolderA-2", + "type" : "folder" + } + ], + "name" : "FolderA", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/macrumors.com" + } + ], + "name" : "FolderB", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/wikipedia.org" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json new file mode 100644 index 0000000000..8ea93679c0 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_ddg_macos.json @@ -0,0 +1,155 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + + ], + "name" : "", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "name" : "Apple (United Kingdom)", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/uk\/" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json new file mode 100644 index 0000000000..58729ad404 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox.json @@ -0,0 +1,180 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Get Help", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/products\/firefox" + }, + { + "name" : "Customize Firefox", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/kb\/customize-firefox-controls-buttons-and-toolbars?utm_source=firefox-browser&utm_medium=default-bookmarks&utm_campaign=customize" + }, + { + "name" : "Get Involved", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/contribute\/" + }, + { + "name" : "About Us", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/about\/" + } + ], + "name" : "Mozilla Firefox", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Getting Started", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/firefox\/central\/" + } + ], + "name" : "Bookmarks Toolbar", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json new file mode 100644 index 0000000000..7294c1c9d9 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_firefox_dirty.json @@ -0,0 +1,42 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Help and Tutorials", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/products\/firefox" + }, + { + "name" : "Customize Firefox", + "type" : "bookmark", + "url" : "https:\/\/support.mozilla.org\/en-US\/kb\/customize-firefox-controls-buttons-and-toolbars" + } + ], + "name" : "Folder Name 1", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + { + "name" : "Get Involved", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/contribute\/" + }, + { + "name" : "About Us", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/about\/" + } + ], + "name" : "Folder Name B", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json new file mode 100644 index 0000000000..ecff3960c3 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_netscape_dirty.json @@ -0,0 +1,31 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Bookmark 1", + "type" : "bookmark", + "url" : "https:\/\/example.com" + }, + { + "name" : "Bookmark 2", + "type" : "bookmark", + "url" : "https:\/\/example2.com" + } + ], + "name" : "Folder 1", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "name" : "Bookmark 3", + "type" : "bookmark", + "url" : "https:\/\/example3.com" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json new file mode 100644 index 0000000000..b7a07d9471 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari.json @@ -0,0 +1,172 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Apple", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/uk" + } + ], + "name" : "Favourites", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + + ], + "name" : "Bookmarks Menu", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "How Danny Trejo Built a Decades-Long Film Career After Prison – Texas Monthly", + "type" : "bookmark", + "url" : "https:\/\/www.texasmonthly.com\/the-culture\/how-danny-trejo-built-decades-long-film-career-after-prison\/" + } + ], + "name" : "Reading List", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "News, sport and opinion from the Guardian's global edition | The Guardian", + "type" : "bookmark", + "url" : "https:\/\/www.theguardian.com\/international" + } + ], + "name" : "FolderA-Level3", + "type" : "folder" + }, + { + "name" : "Digg - What the Internet is talking about right now", + "type" : "bookmark", + "url" : "https:\/\/digg.com\/" + } + ], + "name" : "FolderA-Level2", + "type" : "folder" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + } + ], + "name" : "FolderA-Level1", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "children" : [ + { + "name" : "Bloomberg.com", + "type" : "bookmark", + "url" : "https:\/\/www.bloomberg.com\/europe" + } + ], + "name" : "FolderB-Level3-a", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "TechCrunch – Startup and Technology News", + "type" : "bookmark", + "url" : "https:\/\/techcrunch.com\/" + } + ], + "name" : "FolderB-Level3-b", + "type" : "folder" + }, + { + "name" : "The Verge", + "type" : "bookmark", + "url" : "https:\/\/www.theverge.com\/" + } + ], + "name" : "FolderB-Level2", + "type" : "folder" + }, + { + "name" : "Techmeme", + "type" : "bookmark", + "url" : "https:\/\/techmeme.com\/" + } + ], + "name" : "FolderB-Level1", + "type" : "folder" + }, + { + "children" : [ + + ], + "name" : "EmptyFolder", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "Breaking News | Irish & International Headlines | The Irish Times", + "type" : "bookmark", + "url" : "https:\/\/www.irishtimes.com\/" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "The Wall Street Journal - Breaking News, Business, Financial & Economic News, World News and Video", + "type" : "bookmark", + "url" : "https:\/\/www.wsj.com\/?mod=wsjheader_logo" + } + ], + "name" : "DuplicateFolderName", + "type" : "folder" + }, + { + "name" : "DuckDuckGo — Privacy, simplified.", + "type" : "bookmark", + "url" : "https:\/\/duckduckgo.com\/" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + }, + { + "children" : [ + { + "name" : "MacRumors: Apple News and Rumors", + "type" : "bookmark", + "url" : "https:\/\/www.macrumors.com\/" + } + ], + "name" : "DupeFolderNameContents", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json new file mode 100644 index 0000000000..2a5c599c51 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_safari_tp_dirty.json @@ -0,0 +1,105 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "name" : "Apple", + "type" : "bookmark", + "url" : "https:\/\/www.apple.com\/" + }, + { + "name" : "Yahoo", + "type" : "bookmark", + "url" : "https:\/\/www.icloud.com\/'%3EiCloud%3C\/A%3E%3CD%3E%3CA%20HREF=" + }, + { + "name" : "Google", + "type" : "bookmark", + "url" : "https:\/\/www.bing.com\/'%3EBing%3C\/A%3E%3CDT%3E%3CA%20HREF=" + }, + { + "name" : "Wikipedia", + "type" : "bookmark", + "url" : "https:\/\/www.wikipedia.org\/" + }, + { + "name" : "Facebook", + "type" : "bookmark", + "url" : "https:\/\/www.facebook.com\/" + }, + { + "name" : "Twitter", + "type" : "bookmark", + "url" : "https:\/\/twitter.com\/" + }, + { + "name" : "LinkedIn", + "type" : "bookmark", + "url" : "https:\/\/www.linkedin.com\/" + }, + { + "name" : "The Weather Channel", + "type" : "bookmark", + "url" : "https:\/\/www.weather.com\/" + }, + { + "name" : "Yelp", + "type" : "bookmark", + "url" : "https:\/\/www.yelp.com\/" + }, + { + "name" : "", + "type" : "bookmark", + "url" : "https:\/\/www.tripadvisor.com\/" + }, + { + "name" : "catz - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?q=catz&client=safari&sca_esv=570874343&rls=en&tbm=isch&source=1nms&sa=X&ved=2ahUKEwiQ60DeoN6BAxXJOhAIHbYCD2gQ_AUoAXoECAEQAw&biw=1324&bih=888" + }, + { + "name" : "dogz - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?client=safari&rls=en&q=dogz&ie=UTF-8&oe=UTF-8" + }, + { + "name" : "ab yrvalg - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?client=safari&rls=en&q=abyrvalg&ie=UTF-8&oe=UTF-8" + } + ], + "name" : "Favorites", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + + ], + "name" : "Bookmarks Menu", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "name" : "abyrvalg - Google Search", + "type" : "bookmark", + "url" : "https:\/\/www.google.com\/search?client=safari&rls=en&q=abyrvalg&ie=UTF-8&oe=UTF-8" + } + ], + "name" : "Tab Group Favorites", + "type" : "folder" + } + ], + "name" : "Tab Group Favorites", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json new file mode 100644 index 0000000000..3f933b6bb6 --- /dev/null +++ b/UnitTests/DataImport/__Snapshots__/BookmarksHTMLReaderTests/snapshot.bookmarks_vivaldi.json @@ -0,0 +1,136 @@ +{ + "roots" : { + "bookmark_bar" : { + "children" : [ + { + "children" : [ + { + "name" : "Vivaldi Social", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-social" + }, + { + "name" : "Vivaldi Community", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-community" + }, + { + "name" : "Booking.com", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/bookingcom-en-us" + }, + { + "name" : "Amazon", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/amazon" + }, + { + "name" : "eBay", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/ebay-en-us" + }, + { + "name" : "AliExpress", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/aliexpresscom-us" + }, + { + "name" : "Eneba", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/eneba-en" + }, + { + "name" : "AccuWeather", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-accuweather" + }, + { + "name" : "Vivaldia Games", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/vivaldia-game" + } + ], + "name" : "Speed Dial", + "type" : "folder" + }, + { + "children" : [ + { + "children" : [ + { + "name" : "Vivaldi Features", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-browser-features" + }, + { + "name" : "Vivaldi Webmail", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-webmail" + }, + { + "name" : "Vivaldi Help", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/sd-vivaldi-help-en" + } + ], + "name" : "Vivaldi", + "type" : "folder" + }, + { + "name" : "Booking.com", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/booking-us-bk" + }, + { + "name" : "Kayak", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/kayak-en-us" + }, + { + "name" : "AliExpress", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/ali-us-bk" + }, + { + "name" : "Amazon", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/amazon-us-bk" + }, + { + "name" : "eBay", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/ebay-us-bk" + }, + { + "name" : "Expedia", + "type" : "bookmark", + "url" : "https:\/\/vivaldi.com\/bk\/expedia-eu" + } + ], + "name" : "Bookmarks", + "type" : "folder" + }, + { + "name" : "Get Firefox for desktop — Mozilla (US)", + "type" : "bookmark", + "url" : "https:\/\/www.mozilla.org\/en-US\/firefox\/new\/?redirect_source=firefox-com" + } + ], + "name" : "Bookmarks", + "type" : "folder" + }, + "other" : { + "children" : [ + { + "children" : [ + + ], + "name" : "Trash", + "type" : "folder" + } + ], + "name" : "other", + "type" : "folder" + } + } +} \ No newline at end of file diff --git a/UnitTests/Preferences/PreferencesSidebarModelTests.swift b/UnitTests/Preferences/PreferencesSidebarModelTests.swift index f601cb90f4..1d6666ae91 100644 --- a/UnitTests/Preferences/PreferencesSidebarModelTests.swift +++ b/UnitTests/Preferences/PreferencesSidebarModelTests.swift @@ -31,7 +31,12 @@ final class PreferencesSidebarModelTests: XCTestCase { } private func PreferencesSidebarModel(loadSections: [PreferencesSection]? = nil, tabSwitcherTabs: [Tab.TabContent] = Tab.TabContent.displayableTabTypes) -> DuckDuckGo_Privacy_Browser.PreferencesSidebarModel { - return DuckDuckGo_Privacy_Browser.PreferencesSidebarModel(loadSections: { loadSections ?? PreferencesSection.defaultSections(includingDuckPlayer: false, includingVPN: false) }, tabSwitcherTabs: tabSwitcherTabs, privacyConfigurationManager: MockPrivacyConfigurationManager()) + return DuckDuckGo_Privacy_Browser.PreferencesSidebarModel( + loadSections: { loadSections ?? PreferencesSection.defaultSections(includingDuckPlayer: false, includingSync: false, includingVPN: false) }, + tabSwitcherTabs: tabSwitcherTabs, + privacyConfigurationManager: MockPrivacyConfigurationManager(), + syncService: MockDDGSyncing(authState: .inactive, isSyncInProgress: false) + ) } func testWhenInitializedThenFirstPaneInFirstSectionIsSelected() throws { diff --git a/UnitTests/Sync/SyncPreferencesTests.swift b/UnitTests/Sync/SyncPreferencesTests.swift index cd8d09b957..5317d03c02 100644 --- a/UnitTests/Sync/SyncPreferencesTests.swift +++ b/UnitTests/Sync/SyncPreferencesTests.swift @@ -21,6 +21,7 @@ import Combine import Persistence import SyncUI import XCTest +import TestUtils @testable import BrowserServicesKit @testable import DDGSync @testable import DuckDuckGo_Privacy_Browser @@ -136,11 +137,18 @@ final class SyncPreferencesTests: XCTestCase { } class MockDDGSyncing: DDGSyncing { + let registeredDevices = [RegisteredDevice(id: "1", name: "Device 1", type: "desktop"), RegisteredDevice(id: "2", name: "Device 2", type: "mobile"), RegisteredDevice(id: "3", name: "Device 1", type: "desktop")] var disconnectCalled = false var dataProvidersSource: DataProvidersSource? + @Published var featureFlags: SyncFeatureFlags = .all + + var featureFlagsPublisher: AnyPublisher { + $featureFlags.eraseToAnyPublisher() + } + @Published var authState: SyncAuthState = .inactive var authStatePublisher: AnyPublisher { @@ -151,13 +159,15 @@ class MockDDGSyncing: DDGSyncing { var scheduler: Scheduling + var syncDailyStats = SyncDailyStats(store: MockKeyValueStore()) + @Published var isSyncInProgress: Bool var isSyncInProgressPublisher: AnyPublisher { $isSyncInProgress.eraseToAnyPublisher() } - init(dataProvidersSource: DataProvidersSource? = nil, authState: SyncAuthState, account: SyncAccount? = nil, scheduler: Scheduling, isSyncInProgress: Bool) { + init(dataProvidersSource: DataProvidersSource? = nil, authState: SyncAuthState, account: SyncAccount? = nil, scheduler: Scheduling = CapturingScheduler(), isSyncInProgress: Bool) { self.dataProvidersSource = dataProvidersSource self.authState = authState self.account = account