diff --git a/Package.swift b/Package.swift index b90368633..8f561e30c 100644 --- a/Package.swift +++ b/Package.swift @@ -37,8 +37,8 @@ let package = Package( .package(url: "https://github.com/duckduckgo/TrackerRadarKit", exact: "1.2.1"), .package(url: "https://github.com/duckduckgo/sync_crypto", exact: "0.2.0"), .package(url: "https://github.com/gumob/PunycodeSwift.git", exact: "2.1.0"), - .package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "3.1.0" ), .package(url: "https://github.com/duckduckgo/content-scope-scripts", exact: "4.52.0"), + .package(url: "https://github.com/duckduckgo/privacy-dashboard", exact: "2.0.0"), .package(url: "https://github.com/httpswift/swifter.git", exact: "1.5.0"), .package(url: "https://github.com/duckduckgo/bloom_cpp.git", exact: "3.0.0"), .package(url: "https://github.com/duckduckgo/wireguard-apple", exact: "1.1.1") diff --git a/Sources/Bookmarks/BookmarkUtils.swift b/Sources/Bookmarks/BookmarkUtils.swift index 236b0f2c5..cad0920b7 100644 --- a/Sources/Bookmarks/BookmarkUtils.swift +++ b/Sources/Bookmarks/BookmarkUtils.swift @@ -82,38 +82,6 @@ public struct BookmarkUtils { } } - public static func migrateToFormFactorSpecificFavorites(byCopyingExistingTo folderID: FavoritesFolderID, in context: NSManagedObjectContext) { - assert(folderID != .unified, "You must specify either desktop or mobile folder") - - guard let favoritesFolder = BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.unified.rawValue, in: context) else { - return - } - - if BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.desktop.rawValue, in: context) == nil { - let desktopFavoritesFolder = insertRootFolder(uuid: FavoritesFolderID.desktop.rawValue, into: context) - - if folderID == .desktop { - favoritesFolder.favoritesArray.forEach { bookmark in - bookmark.addToFavorites(favoritesRoot: desktopFavoritesFolder) - } - } else { - desktopFavoritesFolder.shouldManageModifiedAt = false - } - } - - if BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.mobile.rawValue, in: context) == nil { - let mobileFavoritesFolder = insertRootFolder(uuid: FavoritesFolderID.mobile.rawValue, into: context) - - if folderID == .mobile { - favoritesFolder.favoritesArray.forEach { bookmark in - bookmark.addToFavorites(favoritesRoot: mobileFavoritesFolder) - } - } else { - mobileFavoritesFolder.shouldManageModifiedAt = false - } - } - } - public static func copyFavorites( from sourceFolderID: FavoritesFolderID, to targetFolderID: FavoritesFolderID, diff --git a/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift b/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift new file mode 100644 index 000000000..49fce7802 --- /dev/null +++ b/Sources/Bookmarks/Migrations/BookmarkFormFactorFavoritesMigration.swift @@ -0,0 +1,104 @@ +// +// Copyright © 2023 DuckDuckGo. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import Foundation +import CoreData +import Persistence +import Common + +public class BookmarkFormFactorFavoritesMigration { + + public enum MigrationErrors { + case couldNotLoadDatabase + } + + public static func getFavoritesOrderFromPreV4Model(dbContainerLocation: URL, + dbFileURL: URL, + errorEvents: EventMapping? = nil) -> [String]? { + var oldFavoritesOrder: [String]? + + let metadata = try? NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, + at: dbFileURL) + if let metadata, let version = metadata["NSStoreModelVersionHashesVersion"] as? Int, version < 4 { + // Before migrating to latest scheme version, read order of favorites from DB + + let v3BookmarksModel = NSManagedObjectModel.mergedModel(from: [Bookmarks.bundle], forStoreMetadata: metadata)! + + let oldDB = CoreDataDatabase(name: "Bookmarks", + containerLocation: dbContainerLocation, + model: v3BookmarksModel) + oldDB.loadStore { context, error in + guard let context = context else { + errorEvents?.fire(.couldNotLoadDatabase, error: error) + return + } + + let favs = BookmarkUtils.fetchLegacyFavoritesFolder(context) + oldFavoritesOrder = favs?.favoritesArray.compactMap { $0.uuid } + } + } + return oldFavoritesOrder + } + + public static func migrateToFormFactorSpecificFavorites(byCopyingExistingTo folderID: FavoritesFolderID, + preservingOrderOf orderedUUIDs: [String]?, + in context: NSManagedObjectContext) { + assert(folderID != .unified, "You must specify either desktop or mobile folder") + + guard let favoritesFolder = BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.unified.rawValue, in: context) else { + return + } + + if let orderedUUIDs { + // Fix order of favorites + let favorites = favoritesFolder.favoritesArray + + for fav in favorites { + fav.removeFromFavorites(favoritesRoot: favoritesFolder) + } + + for uuid in orderedUUIDs { + if let fav = favorites.first(where: { $0.uuid == uuid}) { + fav.addToFavorites(favoritesRoot: favoritesFolder) + } + } + } + + if BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.desktop.rawValue, in: context) == nil { + let desktopFavoritesFolder = BookmarkUtils.insertRootFolder(uuid: FavoritesFolderID.desktop.rawValue, into: context) + + if folderID == .desktop { + favoritesFolder.favoritesArray.forEach { bookmark in + bookmark.addToFavorites(favoritesRoot: desktopFavoritesFolder) + } + } else { + desktopFavoritesFolder.shouldManageModifiedAt = false + } + } + + if BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.mobile.rawValue, in: context) == nil { + let mobileFavoritesFolder = BookmarkUtils.insertRootFolder(uuid: FavoritesFolderID.mobile.rawValue, into: context) + + if folderID == .mobile { + favoritesFolder.favoritesArray.forEach { bookmark in + bookmark.addToFavorites(favoritesRoot: mobileFavoritesFolder) + } + } else { + mobileFavoritesFolder.shouldManageModifiedAt = false + } + } + } +} diff --git a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift index 475d24931..c86872932 100644 --- a/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift +++ b/Sources/PrivacyDashboard/Model/CookieConsentInfo.swift @@ -24,7 +24,7 @@ public struct CookieConsentInfo: Encodable { let optoutFailed: Bool? let selftestFailed: Bool? let configurable = true - + public init(consentManaged: Bool, cosmetic: Bool?, optoutFailed: Bool?, selftestFailed: Bool?) { self.consentManaged = consentManaged self.cosmetic = cosmetic diff --git a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift index 2fc310faa..22020c81b 100644 --- a/Sources/PrivacyDashboard/Model/ProtectionStatus.swift +++ b/Sources/PrivacyDashboard/Model/ProtectionStatus.swift @@ -20,7 +20,7 @@ import Foundation public struct ProtectionStatus: Encodable { - + let unprotectedTemporary: Bool let enabledFeatures: [String] let allowlisted: Bool diff --git a/Sources/PrivacyDashboard/Model/TrackerInfo.swift b/Sources/PrivacyDashboard/Model/TrackerInfo.swift index 8b5b051b7..9782b349a 100644 --- a/Sources/PrivacyDashboard/Model/TrackerInfo.swift +++ b/Sources/PrivacyDashboard/Model/TrackerInfo.swift @@ -26,11 +26,11 @@ public struct TrackerInfo: Encodable { case requests case installedSurrogates } - + public private (set) var trackers = Set() private(set) var thirdPartyRequests = Set() public private(set) var installedSurrogates = Set() - + public init() { } // MARK: - Collecting detected elements @@ -43,12 +43,12 @@ public struct TrackerInfo: Encodable { public mutating func add(detectedThirdPartyRequest request: DetectedRequest) { thirdPartyRequests.insert(request) } - + public mutating func addInstalledSurrogateHost(_ host: String, for tracker: DetectedRequest, onPageWithURL url: URL) { guard tracker.pageUrl == url.absoluteString else { return } installedSurrogates.insert(host) } - + // MARK: - Helper accessors public var trackersBlocked: [DetectedRequest] { @@ -67,5 +67,5 @@ public struct TrackerInfo: Encodable { try container.encode(allRequests, forKey: .requests) try container.encode(installedSurrogates, forKey: .installedSurrogates) } - + } diff --git a/Sources/PrivacyDashboard/PrivacyDashboardController.swift b/Sources/PrivacyDashboard/PrivacyDashboardController.swift index b3df002c8..085302444 100644 --- a/Sources/PrivacyDashboard/PrivacyDashboardController.swift +++ b/Sources/PrivacyDashboard/PrivacyDashboardController.swift @@ -26,77 +26,58 @@ public enum PrivacyDashboardOpenSettingsTarget: String { case cookiePopupManagement = "cpm" } -/// Navigation delegate for the pages provided by the PrivacyDashboardController -public protocol PrivacyDashboardNavigationDelegate: AnyObject { -#if os(iOS) - func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) -#endif - - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetHeight height: Int) -} - -/// `Report broken site` web page delegate -public protocol PrivacyDashboardReportBrokenSiteDelegate: AnyObject { - - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - reportBrokenSiteDidChangeProtectionSwitch protectionState: ProtectionState) -} - -/// `Privacy Dasboard` web page delegate public protocol PrivacyDashboardControllerDelegate: AnyObject { - - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didChangeProtectionSwitch protectionState: ProtectionState) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didRequestOpenUrlInNewTab url: URL) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestOpenUrlInNewTab url: URL) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didRequestOpenSettings target: PrivacyDashboardOpenSettingsTarget) + +#if os(iOS) + func privacyDashboardControllerDidTapClose(_ privacyDashboardController: PrivacyDashboardController) func privacyDashboardControllerDidRequestShowReportBrokenSite(_ privacyDashboardController: PrivacyDashboardController) - +#endif + #if os(macOS) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - didSetPermission permissionName: String, to state: PermissionAuthorizationState) - func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, - setPermission permissionName: String, paused: Bool) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, didSetHeight height: Int) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + didSetPermission permissionName: String, + to state: PermissionAuthorizationState) + func privacyDashboardController(_ privacyDashboardController: PrivacyDashboardController, + setPermission permissionName: String, + paused: Bool) #endif + } -/// This controller provides two type of user experiences -/// 1- `Privacy Dashboard` with the possibility to navigate to the `Report broken site` page -/// 2- Direct access to the `Report broken site` page -/// Which flow is used is decided at `setup(...)` time, where if `reportBrokenSiteOnly` is true then the `Report broken site` page is opened directly. -@MainActor public final class PrivacyDashboardController: NSObject { +@MainActor +public final class PrivacyDashboardController: NSObject { - // Delegates - public weak var privacyDashboardDelegate: PrivacyDashboardControllerDelegate? - public weak var privacyDashboardNavigationDelegate: PrivacyDashboardNavigationDelegate? - public weak var privacyDashboardReportBrokenSiteDelegate: PrivacyDashboardReportBrokenSiteDelegate? + public weak var delegate: PrivacyDashboardControllerDelegate? @Published public var theme: PrivacyDashboardTheme? public var preferredLocale: String? @Published public var allowedPermissions: [AllowedPermission] = [] - public private(set) weak var privacyInfo: PrivacyInfo? + public private(set) weak var privacyInfo: PrivacyInfo? private weak var webView: WKWebView? + private let privacyDashboardScript = PrivacyDashboardUserScript() private var cancellables = Set() - + public init(privacyInfo: PrivacyInfo?) { self.privacyInfo = privacyInfo } - /// Configure the webview for showing `Privacy Dasboard` or `Report broken site` - /// - Parameters: - /// - webView: The webview to use - /// - reportBrokenSiteOnly: true = `Report broken site`, false = `Privacy Dasboard` - public func setup(for webView: WKWebView, reportBrokenSiteOnly: Bool) { + public func setup(for webView: WKWebView) { self.webView = webView + webView.navigationDelegate = self setupPrivacyDashboardUserScript() - loadPrivacyDashboardHTML(reportBrokenSiteOnly: reportBrokenSiteOnly) + loadPrivacyDashboardHTML() } public func updatePrivacyInfo(_ privacyInfo: PrivacyInfo?) { @@ -131,25 +112,20 @@ public protocol PrivacyDashboardControllerDelegate: AnyObject { privacyDashboardScript.delegate = self webView.configuration.userContentController.addUserScript(privacyDashboardScript.makeWKUserScriptSync()) - + privacyDashboardScript.messageNames.forEach { messageName in webView.configuration.userContentController.add(privacyDashboardScript, name: messageName) } } - private func loadPrivacyDashboardHTML(reportBrokenSiteOnly: Bool) { - guard var url = Bundle.privacyDashboardURL else { return } - - if reportBrokenSiteOnly { - url = url.appendingParameter(name: "screen", value: ProtectionState.EventOriginScreen.breakageForm.rawValue) - } - + private func loadPrivacyDashboardHTML() { + guard let url = Bundle.privacyDashboardURL else { return } webView?.loadFileURL(url, allowingReadAccessTo: url.deletingLastPathComponent().deletingLastPathComponent()) } } extension PrivacyDashboardController: WKNavigationDelegate { - + public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { subscribeToDataModelChanges() @@ -179,7 +155,7 @@ extension PrivacyDashboardController: WKNavigationDelegate { }) .store(in: &cancellables) } - + private func subscribeToTrackerInfo() { privacyInfo?.$trackerInfo .receive(on: DispatchQueue.main) @@ -258,57 +234,53 @@ extension PrivacyDashboardController: WKNavigationDelegate { } extension PrivacyDashboardController: PrivacyDashboardUserScriptDelegate { - + func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenSettings target: String) { let settingsTarget = PrivacyDashboardOpenSettingsTarget(rawValue: target) ?? .general - privacyDashboardDelegate?.privacyDashboardController(self, didRequestOpenSettings: settingsTarget) + delegate?.privacyDashboardController(self, didRequestOpenSettings: settingsTarget) } - + func userScript(_ userScript: PrivacyDashboardUserScript, didChangeProtectionState protectionState: ProtectionState) { - - switch protectionState.eventOrigin.screen { - case .primaryScreen: - privacyDashboardDelegate?.privacyDashboardController(self, didChangeProtectionSwitch: protectionState) - case .breakageForm: - privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, reportBrokenSiteDidChangeProtectionSwitch: protectionState) - } - + delegate?.privacyDashboardController(self, didChangeProtectionSwitch: protectionState) } func userScript(_ userScript: PrivacyDashboardUserScript, didRequestOpenUrlInNewTab url: URL) { - privacyDashboardDelegate?.privacyDashboardController(self, didRequestOpenUrlInNewTab: url) + delegate?.privacyDashboardController(self, didRequestOpenUrlInNewTab: url) } - + func userScriptDidRequestClosing(_ userScript: PrivacyDashboardUserScript) { #if os(iOS) - privacyDashboardNavigationDelegate?.privacyDashboardControllerDidTapClose(self) + delegate?.privacyDashboardControllerDidTapClose(self) #endif } func userScriptDidRequestShowReportBrokenSite(_ userScript: PrivacyDashboardUserScript) { #if os(iOS) - privacyDashboardDelegate?.privacyDashboardControllerDidRequestShowReportBrokenSite(self) + delegate?.privacyDashboardControllerDidRequestShowReportBrokenSite(self) #endif } func userScript(_ userScript: PrivacyDashboardUserScript, setHeight height: Int) { - privacyDashboardNavigationDelegate?.privacyDashboardController(self, didSetHeight: height) +#if os(macOS) + delegate?.privacyDashboardController(self, didSetHeight: height) +#endif } func userScript(_ userScript: PrivacyDashboardUserScript, didRequestSubmitBrokenSiteReportWithCategory category: String, description: String) { - privacyDashboardReportBrokenSiteDelegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, - description: description) +#if os(macOS) + delegate?.privacyDashboardController(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description) +#endif } func userScript(_ userScript: PrivacyDashboardUserScript, didSetPermission permission: String, to state: PermissionAuthorizationState) { #if os(macOS) - privacyDashboardDelegate?.privacyDashboardController(self, didSetPermission: permission, to: state) + delegate?.privacyDashboardController(self, didSetPermission: permission, to: state) #endif } func userScript(_ userScript: PrivacyDashboardUserScript, setPermission permission: String, paused: Bool) { #if os(macOS) - privacyDashboardDelegate?.privacyDashboardController(self, setPermission: permission, paused: paused) + delegate?.privacyDashboardController(self, setPermission: permission, paused: paused) #endif } } diff --git a/Sources/PrivacyDashboard/PrivacyInfo.swift b/Sources/PrivacyDashboard/PrivacyInfo.swift index fd488e382..41d937793 100644 --- a/Sources/PrivacyDashboard/PrivacyInfo.swift +++ b/Sources/PrivacyDashboard/PrivacyInfo.swift @@ -35,7 +35,7 @@ public final class PrivacyInfo { self.url = url self.parentEntity = parentEntity self.protectionStatus = protectionStatus - + trackerInfo = TrackerInfo() } diff --git a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift b/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift index 1df481f4d..77b9c5c5e 100644 --- a/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift +++ b/Sources/PrivacyDashboard/UserScript/PrivacyDashboardUserScript.swift @@ -42,11 +42,11 @@ public enum PrivacyDashboardTheme: String, Encodable { public struct ProtectionState: Decodable { public let isProtected: Bool public let eventOrigin: EventOrigin - + public struct EventOrigin: Decodable { public let screen: EventOriginScreen } - + public enum EventOriginScreen: String, Decodable { case primaryScreen case breakageForm @@ -54,7 +54,7 @@ public struct ProtectionState: Decodable { } final class PrivacyDashboardUserScript: NSObject, StaticUserScript { - + enum MessageNames: String, CaseIterable { case privacyDashboardSetProtection case privacyDashboardSetSize @@ -66,21 +66,21 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { case privacyDashboardSetPermission case privacyDashboardSetPermissionPaused } - + static var injectionTime: WKUserScriptInjectionTime { .atDocumentStart } static var forMainFrameOnly: Bool { false } static var source: String = "" static var script: WKUserScript = PrivacyDashboardUserScript.makeWKUserScript() var messageNames: [String] { MessageNames.allCases.map(\.rawValue) } - + weak var delegate: PrivacyDashboardUserScriptDelegate? - + func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { guard let messageType = MessageNames(rawValue: message.name) else { assertionFailure("PrivacyDashboardUserScript: unexpected message name \(message.name)") return } - + switch messageType { case .privacyDashboardSetProtection: handleSetProtection(message: message) @@ -104,14 +104,14 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { } // MARK: - JS message handlers - + private func handleSetProtection(message: WKScriptMessage) { - + guard let protectionState: ProtectionState = DecodableHelper.decode(from: message.messageBody) else { assertionFailure("privacyDashboardSetProtection: expected ProtectionState") return } - + delegate?.userScript(self, didChangeProtectionState: protectionState) } @@ -121,10 +121,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetHeight: expected height to be an Int") return } - + delegate?.userScript(self, setHeight: height) } - + private func handleClose() { delegate?.userScriptDidRequestClosing(self) } @@ -140,7 +140,7 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetHeight: expected { category: String, description: String }") return } - + delegate?.userScript(self, didRequestSubmitBrokenSiteReportWithCategory: category, description: description) } @@ -152,10 +152,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleOpenUrlInNewTab: expected { url: '...' } ") return } - + delegate?.userScript(self, didRequestOpenUrlInNewTab: url) } - + private func handleOpenSettings(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let target = dict["target"] as? String @@ -163,10 +163,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleOpenSettings: expected { target: '...' } ") return } - + delegate?.userScript(self, didRequestOpenSettings: target) } - + private func handleSetPermission(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let permission = dict["permission"] as? String, @@ -175,10 +175,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("privacyDashboardSetPermission: expected { permission: PermissionType, value: PermissionAuthorizationState }") return } - + delegate?.userScript(self, didSetPermission: permission, to: state) } - + private func handleSetPermissionPaused(message: WKScriptMessage) { guard let dict = message.body as? [String: Any], let permission = dict["permission"] as? String, @@ -187,10 +187,10 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("handleSetPermissionPaused: expected { permission: PermissionType, paused: Bool }") return } - + delegate?.userScript(self, setPermission: permission, paused: paused) } - + // MARK: - Calls to script's JS API func setTrackerInfo(_ tabUrl: URL, trackerInfo: TrackerInfo, webView: WKWebView) { @@ -198,15 +198,15 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { assertionFailure("Can't encode trackerInfoViewModel into JSON") return } - + guard let safeTabUrl = try? JSONEncoder().encode(tabUrl).utf8String() else { assertionFailure("Can't encode tabUrl into JSON") return } - + evaluate(js: "window.onChangeRequestData(\(safeTabUrl), \(trackerBlockingDataJson))", in: webView) } - + func setProtectionStatus(_ protectionStatus: ProtectionStatus, webView: WKWebView) { guard let protectionStatusJson = try? JSONEncoder().encode(protectionStatus).utf8String() else { assertionFailure("Can't encode mockProtectionStatus into JSON") @@ -215,42 +215,42 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { evaluate(js: "window.onChangeProtectionStatus(\(protectionStatusJson))", in: webView) } - + func setUpgradedHttps(_ upgradedHttps: Bool, webView: WKWebView) { evaluate(js: "window.onChangeUpgradedHttps(\(upgradedHttps))", in: webView) } - + func setParentEntity(_ parentEntity: Entity?, webView: WKWebView) { if parentEntity == nil { return } - + guard let parentEntityJson = try? JSONEncoder().encode(parentEntity).utf8String() else { assertionFailure("Can't encode parentEntity into JSON") return } - + evaluate(js: "window.onChangeParentEntity(\(parentEntityJson))", in: webView) } - + func setTheme(_ theme: PrivacyDashboardTheme?, webView: WKWebView) { if theme == nil { return } - + guard let themeJson = try? JSONEncoder().encode(theme).utf8String() else { assertionFailure("Can't encode themeName into JSON") return } - + evaluate(js: "window.onChangeTheme(\(themeJson))", in: webView) } - + func setServerTrust(_ serverTrustViewModel: ServerTrustViewModel, webView: WKWebView) { guard let certificateDataJson = try? JSONEncoder().encode(serverTrustViewModel).utf8String() else { assertionFailure("Can't encode serverTrustViewModel into JSON") return } - + evaluate(js: "window.onChangeCertificateData(\(certificateDataJson))", in: webView) } - + func setIsPendingUpdates(_ isPendingUpdates: Bool, webView: WKWebView) { evaluate(js: "window.onIsPendingUpdates(\(isPendingUpdates))", in: webView) } @@ -283,17 +283,17 @@ final class PrivacyDashboardUserScript: NSObject, StaticUserScript { self.evaluate(js: "window.onChangeAllowedPermissions(\(allowedPermissionsJson))", in: webView) } - + private func evaluate(js: String, in webView: WKWebView) { webView.evaluateJavaScript(js) } - + } extension Data { - + func utf8String() -> String? { return String(data: self, encoding: .utf8) } - + } diff --git a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift b/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift index 0862cb89d..eb94d1db3 100644 --- a/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift +++ b/Sources/PrivacyDashboard/ViewModel/ServerTrustViewModel.swift @@ -19,28 +19,28 @@ import Foundation public struct ServerTrustViewModel: Encodable { - + struct SecCertificateViewModel: Encodable { - + let summary: String? let commonName: String? let emails: [String]? let publicKey: SecKeyViewModel? - + public init(secCertificate: SecCertificate) { summary = SecCertificateCopySubjectSummary(secCertificate) as String? ?? "" - + var commonName: CFString? SecCertificateCopyCommonName(secCertificate, &commonName) self.commonName = commonName as String? ?? "" - + var emails: CFArray? if errSecSuccess == SecCertificateCopyEmailAddresses(secCertificate, &emails) { self.emails = emails as? [String] } else { self.emails = nil } - + var secTrust: SecTrust? if errSecSuccess == SecTrustCreateWithCertificates(secCertificate, SecPolicyCreateBasicX509(), &secTrust), let certTrust = secTrust { if #available(iOS 14.0, macOS 11.0, *) { @@ -53,11 +53,11 @@ public struct ServerTrustViewModel: Encodable { publicKey = nil } } - + } - + struct SecKeyViewModel: Encodable { - + static func typeToString(_ type: String) -> String? { switch type as CFString { case kSecAttrKeyTypeRSA: return "RSA" @@ -66,14 +66,14 @@ public struct ServerTrustViewModel: Encodable { default: return nil } } - + let keyId: Data? let externalRepresentation: Data? - + let bitSize: Int? let blockSize: Int? let effectiveSize: Int? - + let canDecrypt: Bool let canDerive: Bool let canEncrypt: Bool @@ -81,20 +81,20 @@ public struct ServerTrustViewModel: Encodable { let canUnwrap: Bool let canVerify: Bool let canWrap: Bool - + let isPermanent: Bool? let type: String? - + init?(secKey: SecKey?) { guard let secKey = secKey else { return nil } - + blockSize = SecKeyGetBlockSize(secKey) externalRepresentation = SecKeyCopyExternalRepresentation(secKey, nil) as Data? - + let attrs: NSDictionary? = SecKeyCopyAttributes(secKey) - + bitSize = attrs?[kSecAttrKeySizeInBits] as? Int effectiveSize = attrs?[kSecAttrEffectiveKeySize] as? Int canDecrypt = attrs?[kSecAttrCanDecrypt] as? Bool ?? false @@ -106,36 +106,36 @@ public struct ServerTrustViewModel: Encodable { canWrap = attrs?[kSecAttrCanWrap] as? Bool ?? false isPermanent = attrs?[kSecAttrIsPermanent] as? Bool ?? false keyId = attrs?[kSecAttrApplicationLabel] as? Data - + if let type = attrs?[kSecAttrType] as? String { self.type = Self.typeToString(type) } else { self.type = nil } - + } - + } - + let secCertificateViewModels: [SecCertificateViewModel] - + public init?(serverTrust: SecTrust?) { guard let serverTrust = serverTrust else { return nil } - + let secTrust = serverTrust let count = SecTrustGetCertificateCount(secTrust) guard count != 0 else { return nil } - + var secCertificateViewModels = [SecCertificateViewModel]() for i in 0 ..< count { guard let certificate = SecTrustGetCertificateAtIndex(secTrust, i) else { return nil } let certificateViewModel = SecCertificateViewModel(secCertificate: certificate) secCertificateViewModels.append(certificateViewModel) } - + self.secCertificateViewModels = secCertificateViewModels } - + } diff --git a/Tests/BookmarksTests/BookmarkUtilsTests.swift b/Tests/BookmarksTests/BookmarkUtilsTests.swift index 75266145b..8588e4c44 100644 --- a/Tests/BookmarksTests/BookmarkUtilsTests.swift +++ b/Tests/BookmarksTests/BookmarkUtilsTests.swift @@ -61,6 +61,9 @@ final class BookmarkUtilsTests: XCTestCase { let bookmarkTree = BookmarkTree { Bookmark(id: "1") Bookmark(id: "2", favoritedOn: [.unified]) + Folder(id: "10") { + Bookmark(id: "12", favoritedOn: [.unified]) + } Bookmark(id: "3", favoritedOn: [.unified]) Bookmark(id: "4", favoritedOn: [.unified]) } @@ -69,15 +72,27 @@ final class BookmarkUtilsTests: XCTestCase { bookmarkTree.createEntities(in: context) try! context.save() + let favoritesArray = BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.unified.rawValue, in: context)?.favoritesArray.compactMap(\.uuid) - BookmarkUtils.migrateToFormFactorSpecificFavorites(byCopyingExistingTo: .mobile, in: context) + BookmarkFormFactorFavoritesMigration + .migrateToFormFactorSpecificFavorites( + byCopyingExistingTo: .mobile, + preservingOrderOf: nil, + in: context + ) try! context.save() + let mobileFavoritesArray = BookmarkUtils.fetchFavoritesFolder(withUUID: FavoritesFolderID.mobile.rawValue, in: context)?.favoritesArray.compactMap(\.uuid) + XCTAssertEqual(favoritesArray, mobileFavoritesArray) + let rootFolder = BookmarkUtils.fetchRootFolder(context)! assertEquivalent(withTimestamps: false, rootFolder, BookmarkTree { Bookmark(id: "1") Bookmark(id: "2", favoritedOn: [.mobile, .unified]) + Folder(id: "10") { + Bookmark(id: "12", favoritedOn: [.mobile, .unified]) + } Bookmark(id: "3", favoritedOn: [.mobile, .unified]) Bookmark(id: "4", favoritedOn: [.mobile, .unified]) })