diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index ab5be088cd..0049b7cc8f 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -66,6 +66,18 @@ 1DB9617B29F1D06D00CF5568 /* InternalUserDeciderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB9617929F1D06D00CF5568 /* InternalUserDeciderMock.swift */; }; 1DB9618229F67F6100CF5568 /* FaviconNullStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB9617F29F67F3E00CF5568 /* FaviconNullStore.swift */; }; 1DB9618329F67F6200CF5568 /* FaviconNullStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DB9617F29F67F3E00CF5568 /* FaviconNullStore.swift */; }; + 1DC776322AF436C7002DF07B /* OnboardingSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776312AF436C7002DF07B /* OnboardingSchemeHandler.swift */; }; + 1DC776332AF43812002DF07B /* OnboardingSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776312AF436C7002DF07B /* OnboardingSchemeHandler.swift */; }; + 1DC776352AF43855002DF07B /* OnboardingSchemeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776312AF436C7002DF07B /* OnboardingSchemeHandler.swift */; }; + 1DC776382AF4416A002DF07B /* OnboardingTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776372AF4416A002DF07B /* OnboardingTabExtension.swift */; }; + 1DC7763A2AF44255002DF07B /* OnboardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776392AF44255002DF07B /* OnboardingManager.swift */; }; + 1DC7763B2AF445BD002DF07B /* OnboardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776392AF44255002DF07B /* OnboardingManager.swift */; }; + 1DC7763D2AF445C0002DF07B /* OnboardingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776392AF44255002DF07B /* OnboardingManager.swift */; }; + 1DC776402AF445CB002DF07B /* OnboardingTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776372AF4416A002DF07B /* OnboardingTabExtension.swift */; }; + 1DC776412AF445CD002DF07B /* OnboardingTabExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776372AF4416A002DF07B /* OnboardingTabExtension.swift */; }; + 1DC776452AF4F1C6002DF07B /* OnboardingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776442AF4F1C6002DF07B /* OnboardingUserScript.swift */; }; + 1DC776462AF4F1C6002DF07B /* OnboardingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776442AF4F1C6002DF07B /* OnboardingUserScript.swift */; }; + 1DC776482AF4F1C6002DF07B /* OnboardingUserScript.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DC776442AF4F1C6002DF07B /* OnboardingUserScript.swift */; }; 1DCFBC8A29ADF32B00313531 /* BurnerHomePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */; }; 1DCFBC8B29ADF32B00313531 /* BurnerHomePageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */; }; 1DDF076328F815AD00EDFBE3 /* BWCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DDF075D28F815AD00EDFBE3 /* BWCommunicator.swift */; }; @@ -3199,6 +3211,10 @@ 1DA6D0FF2A1FF9DC00540406 /* HTTPCookieTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPCookieTests.swift; sourceTree = ""; }; 1DB9617929F1D06D00CF5568 /* InternalUserDeciderMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternalUserDeciderMock.swift; sourceTree = ""; }; 1DB9617F29F67F3E00CF5568 /* FaviconNullStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FaviconNullStore.swift; sourceTree = ""; }; + 1DC776312AF436C7002DF07B /* OnboardingSchemeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingSchemeHandler.swift; sourceTree = ""; }; + 1DC776372AF4416A002DF07B /* OnboardingTabExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingTabExtension.swift; sourceTree = ""; }; + 1DC776392AF44255002DF07B /* OnboardingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingManager.swift; sourceTree = ""; }; + 1DC776442AF4F1C6002DF07B /* OnboardingUserScript.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingUserScript.swift; sourceTree = ""; }; 1DCFBC8929ADF32B00313531 /* BurnerHomePageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BurnerHomePageView.swift; sourceTree = ""; }; 1DDF075C28F815AD00EDFBE3 /* BWCredential.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWCredential.swift; sourceTree = ""; }; 1DDF075D28F815AD00EDFBE3 /* BWCommunicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BWCommunicator.swift; sourceTree = ""; }; @@ -6193,6 +6209,9 @@ children = ( 85707F2F276A7DB000DC0649 /* ViewModel */, 85B7184827677A9200B4277F /* View */, + 1DC776312AF436C7002DF07B /* OnboardingSchemeHandler.swift */, + 1DC776392AF44255002DF07B /* OnboardingManager.swift */, + 1DC776442AF4F1C6002DF07B /* OnboardingUserScript.swift */, ); path = Onboarding; sourceTree = ""; @@ -7660,6 +7679,7 @@ B66260E529ACAE4B00E9E3EE /* NavigationHotkeyHandler.swift */, B66260DC29AC5D4300E9E3EE /* NavigationProtectionTabExtension.swift */, B626A76C29928B1600053070 /* TestsClosureNavigationResponder.swift */, + 1DC776372AF4416A002DF07B /* OnboardingTabExtension.swift */, ); path = TabExtensions; sourceTree = ""; @@ -8449,8 +8469,6 @@ buildRules = ( ); dependencies = ( - B692D0DF2B209FB7003F2548 /* PBXTargetDependency */, - 4B5F14FC2A15291D0060320F /* PBXTargetDependency */, 31C6E9AD2B0C07BA0086DC30 /* PBXTargetDependency */, ); name = "DuckDuckGo Privacy Browser"; @@ -9322,6 +9340,7 @@ 3706FAC0293F65D500E42796 /* DataTaskProviding.swift in Sources */, 3706FAC1293F65D500E42796 /* FeedbackViewController.swift in Sources */, 3706FAC2293F65D500E42796 /* FaviconSelector.swift in Sources */, + 1DC7763B2AF445BD002DF07B /* OnboardingManager.swift in Sources */, 3706FAC3293F65D500E42796 /* AddEditFavoriteViewController.swift in Sources */, B696AFFC2AC5924800C93203 /* FileLineError.swift in Sources */, B6E1491129A5C30A00AAFBE8 /* FBProtectionTabExtension.swift in Sources */, @@ -9377,6 +9396,7 @@ 3706FAF3293F65D500E42796 /* LocalAuthenticationService.swift in Sources */, 1D36E659298AA3BA00AA485D /* InternalUserDeciderStore.swift in Sources */, B6BCC5242AFCDABB002C5499 /* DataImportSourceViewModel.swift in Sources */, + 1DC776332AF43812002DF07B /* OnboardingSchemeHandler.swift in Sources */, 3706FEBC293F6EFF00E42796 /* BWResponse.swift in Sources */, 3706FAF4293F65D500E42796 /* SafariBookmarksReader.swift in Sources */, 31C9ADE62AF0564500CEF57D /* WaitlistFeatureSetupHandler.swift in Sources */, @@ -9392,6 +9412,7 @@ 3706FAFA293F65D500E42796 /* CleanThisHistoryMenuItem.swift in Sources */, 1DA6D0FE2A1FF9A100540406 /* HTTPCookie.swift in Sources */, 3706FAFC293F65D500E42796 /* DownloadListItem.swift in Sources */, + 1DC776462AF4F1C6002DF07B /* OnboardingUserScript.swift in Sources */, 3706FAFD293F65D500E42796 /* DownloadsPopover.swift in Sources */, 3706FAFE293F65D500E42796 /* SpacerNode.swift in Sources */, 3706FB00293F65D500E42796 /* PasswordManagementCreditCardModel.swift in Sources */, @@ -9711,6 +9732,7 @@ 3706FBF3293F65D500E42796 /* PseudoFolder.swift in Sources */, 3706FBF4293F65D500E42796 /* Visit.swift in Sources */, 3706FBF5293F65D500E42796 /* PixelDataStore.swift in Sources */, + 1DC776402AF445CB002DF07B /* OnboardingTabExtension.swift in Sources */, 3706FBF6293F65D500E42796 /* Pixel.swift in Sources */, 3706FBF7293F65D500E42796 /* PixelEvent.swift in Sources */, 3706FBF8293F65D500E42796 /* TabBarFooter.swift in Sources */, @@ -10368,6 +10390,7 @@ 4B95794A2AC7AE700062CA31 /* TabIndex.swift in Sources */, 4B95794B2AC7AE700062CA31 /* SavePanelAccessoryView.swift in Sources */, 4B95794C2AC7AE700062CA31 /* TabLazyLoaderDataSource.swift in Sources */, + 1DC776352AF43855002DF07B /* OnboardingSchemeHandler.swift in Sources */, 4B95794D2AC7AE700062CA31 /* LoginImport.swift in Sources */, 4B95794E2AC7AE700062CA31 /* JoinWaitlistView.swift in Sources */, 4B95794F2AC7AE700062CA31 /* LazyLoadable.swift in Sources */, @@ -10947,6 +10970,7 @@ 4B957B642AC7AE700062CA31 /* BackForwardListItem.swift in Sources */, 4B957B672AC7AE700062CA31 /* AtbAndVariantCleanup.swift in Sources */, 4B957B682AC7AE700062CA31 /* NibLoadable.swift in Sources */, + 1DC776412AF445CD002DF07B /* OnboardingTabExtension.swift in Sources */, 4B957B692AC7AE700062CA31 /* FeedbackWindow.swift in Sources */, 4B957B6A2AC7AE700062CA31 /* WorkspaceProtocol.swift in Sources */, 4B957B6B2AC7AE700062CA31 /* RecentlyVisitedView.swift in Sources */, @@ -10981,6 +11005,7 @@ 4B957B842AC7AE700062CA31 /* SuggestionContainer.swift in Sources */, 4B957B852AC7AE700062CA31 /* FindInPageTabExtension.swift in Sources */, 4B957B862AC7AE700062CA31 /* HomePageViewController.swift in Sources */, + 1DC7763D2AF445C0002DF07B /* OnboardingManager.swift in Sources */, 4B957B882AC7AE700062CA31 /* OperatingSystemVersionExtension.swift in Sources */, 4B957B892AC7AE700062CA31 /* ToggleableScrollView.swift in Sources */, 4B957B8A2AC7AE700062CA31 /* TabCleanupPreparer.swift in Sources */, @@ -11040,6 +11065,7 @@ 4B957BB92AC7AE700062CA31 /* SyncErrorHandler.swift in Sources */, 4BF0E50A2AD2551A00FFEC9E /* NetworkProtectionPixelEvent.swift in Sources */, 4B957BBA2AC7AE700062CA31 /* URLExtension.swift in Sources */, + 1DC776482AF4F1C6002DF07B /* OnboardingUserScript.swift in Sources */, 4B957BBB2AC7AE700062CA31 /* Tab+UIDelegate.swift in Sources */, 1E2AE4C92ACB217800684E0A /* NetworkProtectionRemoteMessagingStorage.swift in Sources */, 4B957BBD2AC7AE700062CA31 /* NSStoryboardExtension.swift in Sources */, @@ -11369,6 +11395,7 @@ 37CD54CA27F2FDD100F1F7B9 /* AutofillPreferencesModel.swift in Sources */, 7BBD45B12A691AB500C83CA9 /* NetworkProtectionDebugUtilities.swift in Sources */, B657841F25FA497600D8DB33 /* NSException+Catch.swift in Sources */, + 1DC776382AF4416A002DF07B /* OnboardingTabExtension.swift in Sources */, 4BE65481271FCD4D008D1D63 /* PasswordManagementNoteModel.swift in Sources */, 3184AC6F288F2A1100C35E4B /* CookieNotificationAnimationModel.swift in Sources */, 4B9DB0382A983B24000927DB /* JoinedWaitlistView.swift in Sources */, @@ -11565,6 +11592,7 @@ B64C853026943BC10048FEBE /* Permissions.xcdatamodeld in Sources */, EE339228291BDEFD009F62C1 /* JSAlertController.swift in Sources */, 4B9DB04A2A983B24000927DB /* NotificationService.swift in Sources */, + 1DC776452AF4F1C6002DF07B /* OnboardingUserScript.swift in Sources */, 3775912D29AAC72700E26367 /* SyncPreferences.swift in Sources */, 1DB9618329F67F6200CF5568 /* FaviconNullStore.swift in Sources */, BB5789722B2CA70F0009DFE2 /* DataBrokerProtectionSubscriptionEventHandler.swift in Sources */, @@ -11718,6 +11746,7 @@ AA6820EB25503D6A005ED0D5 /* Fire.swift in Sources */, 3158B1492B0BF73000AF130C /* DBPHomeViewController.swift in Sources */, 37445F9C2A1569F00029F789 /* SyncBookmarksAdapter.swift in Sources */, + 1DC776322AF436C7002DF07B /* OnboardingSchemeHandler.swift in Sources */, B6AAAC3E26048F690029438D /* RandomAccessCollectionExtension.swift in Sources */, 4B9292AF26670F5300AD2C21 /* NSOutlineViewExtensions.swift in Sources */, AA585D82248FD31100E9A3E2 /* AppDelegate.swift in Sources */, @@ -11736,6 +11765,7 @@ B65E6B9E26D9EC0800095F96 /* CircularProgressView.swift in Sources */, AABEE69C24A902BB0043105B /* SuggestionContainer.swift in Sources */, B6C00ECD292F89D9009C73A6 /* FindInPageTabExtension.swift in Sources */, + 1DC7763A2AF44255002DF07B /* OnboardingManager.swift in Sources */, 85589E8327BBB8630038AD11 /* HomePageViewController.swift in Sources */, B6A9E46B2614618A0067D1B9 /* OperatingSystemVersionExtension.swift in Sources */, 4BDFA4AE27BF19E500648192 /* ToggleableScrollView.swift in Sources */, @@ -12103,10 +12133,6 @@ isa = PBXTargetDependency; productRef = 4B4D60522A0B29CB00BCD287 /* NetworkProtection */; }; - 4B5F14FC2A15291D0060320F /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = 4B5F14FB2A15291D0060320F /* InputFilesChecker */; - }; 4B5F14FE2A1529230060320F /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = 4B5F14FD2A1529230060320F /* InputFilesChecker */; @@ -12162,10 +12188,6 @@ isa = PBXTargetDependency; productRef = B6080BAA2B20AF9200B418EF /* SwiftLintPlugin */; }; - B692D0DF2B209FB7003F2548 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - productRef = B692D0DE2B209FB7003F2548 /* SwiftLintPlugin */; - }; B69D06142A4C0AC50032D14D /* PBXTargetDependency */ = { isa = PBXTargetDependency; productRef = B69D06132A4C0AC50032D14D /* SwiftLintPlugin */; diff --git a/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift b/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift index c5ee1892a5..bc06b85888 100644 --- a/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift +++ b/DuckDuckGo/Common/Extensions/WKWebViewConfigurationExtensions.swift @@ -43,6 +43,9 @@ extension WKWebViewConfiguration { if urlSchemeHandler(forURLScheme: DuckPlayer.duckPlayerScheme) == nil { setURLSchemeHandler(DuckPlayerSchemeHandler(), forURLScheme: DuckPlayer.duckPlayerScheme) } + if urlSchemeHandler(forURLScheme: OnboardingManager.urlScheme) == nil { + setURLSchemeHandler(OnboardingSchemeHandler(), forURLScheme: OnboardingManager.urlScheme) + } } let userContentController = UserContentController(assetsPublisher: contentBlocking.contentBlockingAssetsPublisher, diff --git a/DuckDuckGo/Onboarding/OnboardingManager.swift b/DuckDuckGo/Onboarding/OnboardingManager.swift new file mode 100644 index 0000000000..7fc3d115e5 --- /dev/null +++ b/DuckDuckGo/Onboarding/OnboardingManager.swift @@ -0,0 +1,34 @@ +// +// OnboardingManager.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 Foundation + +final class OnboardingManager { + static let urlScheme = "onboarding" + + var isEnabled: Bool { + true + } +} + +extension URL { + + var isOnboardingScheme: Bool { + scheme == OnboardingManager.urlScheme + } +} diff --git a/DuckDuckGo/Onboarding/OnboardingSchemeHandler.swift b/DuckDuckGo/Onboarding/OnboardingSchemeHandler.swift new file mode 100644 index 0000000000..8efb983b29 --- /dev/null +++ b/DuckDuckGo/Onboarding/OnboardingSchemeHandler.swift @@ -0,0 +1,80 @@ +// +// OnboardingSchemeHandler.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 Foundation +import WebKit +import ContentScopeScripts + +final class OnboardingSchemeHandler: NSObject, WKURLSchemeHandler { + + func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) { + guard let requestURL = urlSchemeTask.request.url else { + assertionFailure("No URL for Privacy Debug Tools scheme handler") + return + } + + guard let (response, data) = response(for: requestURL) else { return } + + urlSchemeTask.didReceive(response) + urlSchemeTask.didReceive(data) + urlSchemeTask.didFinish() + } + + func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {} + + func response(for url: URL) -> (URLResponse, Data)? { + var fileName = "index" + var fileExtension = "html" + var directoryURL = URL(fileURLWithPath: "/pages/onboarding") + directoryURL.appendPathComponent(url.path) + + if !directoryURL.pathExtension.isEmpty { + fileExtension = directoryURL.pathExtension + directoryURL.deletePathExtension() + fileName = directoryURL.lastPathComponent + directoryURL.deleteLastPathComponent() + } + + print(directoryURL.path, fileName, fileExtension) + + guard let file = ContentScopeScripts.Bundle.path(forResource: fileName, ofType: fileExtension, inDirectory: directoryURL.path) else { + assertionFailure("HTML template not found") + return nil + } + + guard let data = try? Data(contentsOf: URL(fileURLWithPath: file)) else { + return nil + } + + let mimeType: String? = { + switch fileExtension { + case "html": return "text/html" + case "css": return "text/css" + case "js": return "text/javascript" + case "png": return "image/png" + case "jpg", "jpeg": return "image/jpeg" + case "gif": return "image/gif" + case "svg": return "image/svg+xml" + default: return nil + } + }() + + let response = URLResponse(url: url, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil) + return (response, data) + } + +} diff --git a/DuckDuckGo/Onboarding/OnboardingUserScript.swift b/DuckDuckGo/Onboarding/OnboardingUserScript.swift new file mode 100644 index 0000000000..0af1e0e0a4 --- /dev/null +++ b/DuckDuckGo/Onboarding/OnboardingUserScript.swift @@ -0,0 +1,253 @@ +// +// OnboardingUserScript.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 BrowserServicesKit +import Configuration +import WebKit +import Common +import UserScript + +final class OnboardingUserScript: NSObject, Subfeature { + + let messageOriginPolicy: MessageOriginPolicy = .all + let featureName: String = "onboarding" + var broker: UserScriptMessageBroker? + + // MARK: - Subfeature + public func with(broker: UserScriptMessageBroker) { + self.broker = broker + } + + // MARK: - MessageNames + enum MessageNames: String, CaseIterable { + case setBlockCookiePopups + case setDuckPlayer + case setBookmarksBar + case setSessionRestore + case setShowHomeButton + case requestRemoveFromDock + case requestImport + case requestSetAsDefault + case dismiss + case dismissToSettings + case dismissToAddressBar + case reportPageException + } + + func handler(forMethodNamed methodName: String) -> Subfeature.Handler? { + switch MessageNames(rawValue: methodName) { + case .setDuckPlayer: + return setDuckPlayer + case .setBookmarksBar: + return setBookmarksBar + case .setSessionRestore: + return setSessionRestore + case .setShowHomeButton: + return setShowHome + case .requestImport: + return requestImport + case .requestSetAsDefault: + return requestSetAsDefault + case .requestRemoveFromDock: + return requestRemoveFromDock + case .setBlockCookiePopups: + return setBlockCookiePopups + case .dismissToAddressBar: + return dismissToAddressBar + case .dismissToSettings: + return dismissToSettings + case .reportPageException: + return reportPageException + default: + print(methodName) + // assertionFailure("PrivacyConfigurationEditUserScript: Failed to parse User Script message: \(methodName)") + return nil + } + } + + struct BooleanParams: Codable { + let enabled: Bool + } + + struct StringParams: Codable { + let value: String + } + + func parse(params: Any) -> BooleanParams? { + guard let data = try? JSONSerialization.data(withJSONObject: params), + let result = try? JSONDecoder().decode(BooleanParams.self, from: data) else { + return nil + } + return result + } + + @MainActor + func setDuckPlayer(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let params = parse(params: params) + guard params?.enabled == true else { return nil } + + DuckPlayerPreferences.shared.duckPlayerMode = .enabled + return nil + } + + @MainActor + func setBookmarksBar(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let params: BooleanParams = DecodableHelper.decode(from: params) else { return nil } + AppearancePreferences.shared.showBookmarksBar = params.enabled + return nil + } + + @MainActor + func setSessionRestore(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let params: BooleanParams = DecodableHelper.decode(from: params) else { return nil } + StartupPreferences.shared.restorePreviousSession = params.enabled + return nil + } + + @MainActor + func setBlockCookiePopups(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let params = parse(params: params) + guard params?.enabled == true else { return nil } + + PrivacySecurityPreferences.shared.autoconsentEnabled = true + return nil + } + + enum ShowHomeValue: String, Decodable { + case hidden; + case left; + case right; + } + + struct ShowHomeParams: Decodable { + let value: ShowHomeValue + } + + @MainActor + func setShowHome(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let params: ShowHomeParams = DecodableHelper.decode(from: params), + let value = HomeButtonPosition(rawValue: params.value.rawValue) + else { return nil } + + StartupPreferences.shared.homeButtonPosition = value + StartupPreferences.shared.updateHomeButton() + return nil + } + + @MainActor + func requestImport(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let response: Response = try await withCheckedThrowingContinuation { continuation in + DataImportViewController.show { + let response = Response() + continuation.resume(returning: response) + } + } + return response + } + + struct Response: Encodable { + } + + @MainActor + func requestSetAsDefault(params: Any, original: WKScriptMessage) async throws -> Encodable? { + let defaultBrowserPreferences = DefaultBrowserPreferences() + if defaultBrowserPreferences.isDefault { + return Response() + } + + let response: Response = try await withCheckedThrowingContinuation { continuation in + defaultBrowserPreferences.becomeDefault { _ in + _ = defaultBrowserPreferences + let response = Response() + continuation.resume(returning: response) + } + } + return response + } + + @MainActor + func requestRemoveFromDock(params: Any, original: WKScriptMessage) async throws -> Encodable? { + print("todo: requestRemoveFromDock...") + return Response() + } + + @MainActor + func dismissToAddressBar(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let mainVC = WindowControllersManager.shared.lastKeyMainWindowController?.mainViewController else { return nil } + mainVC.navigationBarViewController.addressBarViewController?.addressBarTextField.stringValue = "" + mainVC.navigationBarViewController.addressBarViewController?.addressBarTextField.makeMeFirstResponder() + return nil + } + + @MainActor + func dismissToSettings(params: Any, original: WKScriptMessage) async throws -> Encodable? { + print("todo: dismissToSettings pixel?") + return nil + } + + struct PageException: Decodable { + let message: String + let page: String + } + + struct InitException: Decodable { + let message: String + let page: String + } + + @MainActor + func reportPageException(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let exception: PageException = DecodableHelper.decode(from: params) else { return nil } + print("todo: reportPageException pixel?") + return nil + } + + @MainActor + func reportInitException(params: Any, original: WKScriptMessage) async throws -> Encodable? { + guard let exception: InitException = DecodableHelper.decode(from: params) else { return nil } + print("todo: reportInitException pixel?") + return nil + } +} + +struct DecodableHelper { + public static func decode(from input: Input) -> Target? { + do { + let json = try JSONSerialization.data(withJSONObject: input) + return try JSONDecoder().decode(Target.self, from: json) + } catch let error as DecodingError { + switch error { + case .typeMismatch(let type, let context): + os_log(.error, "DecodableHelper: Type Mismatch for type \(type): \(context.debugDescription) \(context.codingPath)") + case .valueNotFound(let type, let context): + os_log(.error, "DecodableHelper: Value not found for type \(type): \(context.debugDescription) \(context.codingPath)") + case .keyNotFound(let key, let context): + os_log(.error, "DecodableHelper: Key not found \(key): \(context.debugDescription) \(context.codingPath)") + case .dataCorrupted(let context): + os_log(.error, "DecodableHelper: Data corrupted: \(context.debugDescription) \(context.codingPath)") + default: + os_log(.error, "DecodableHelper: Error decoding message body: %{public}@", error.localizedDescription) + } + return nil + } catch { + os_log(.error, "DecodableHelper: Unknown error: %{public}@", error.localizedDescription) + return nil + } + + } +} diff --git a/DuckDuckGo/Tab/Model/Tab+Navigation.swift b/DuckDuckGo/Tab/Model/Tab+Navigation.swift index 2ddb6383b8..489766d0ff 100644 --- a/DuckDuckGo/Tab/Model/Tab+Navigation.swift +++ b/DuckDuckGo/Tab/Model/Tab+Navigation.swift @@ -41,6 +41,7 @@ extension Tab: NavigationResponder { func setupNavigationDelegate() { navigationDelegate.setResponders( .weak(nullable: self.navigationHotkeyHandler), + .weak(nullable: self.onboarding), .weak(self), diff --git a/DuckDuckGo/Tab/Model/Tab.swift b/DuckDuckGo/Tab/Model/Tab.swift index 98d91dac6d..c7bb846bca 100644 --- a/DuckDuckGo/Tab/Model/Tab.swift +++ b/DuckDuckGo/Tab/Model/Tab.swift @@ -46,6 +46,7 @@ protocol NewWindowPolicyDecisionMaker { func decideNewWindowPolicy(for navigationAction: WKNavigationAction) -> NavigationDecision? } +// swiftlint:disable file_length // swiftlint:disable:next type_body_length @dynamicMemberLookup final class Tab: NSObject, Identifiable, ObservableObject { @@ -240,6 +241,7 @@ protocol NewWindowPolicyDecisionMaker { var cbaTimeReporter: ContentBlockingAssetsCompilationTimeReporter? let duckPlayer: DuckPlayer var downloadManager: FileDownloadManagerProtocol + let onboardingManager: OnboardingManager } fileprivate weak var delegate: TabDelegate? @@ -281,6 +283,7 @@ protocol NewWindowPolicyDecisionMaker { workspace: Workspace = NSWorkspace.shared, privacyFeatures: AnyPrivacyFeatures? = nil, duckPlayer: DuckPlayer? = nil, + onboardingManager: OnboardingManager? = nil, downloadManager: FileDownloadManagerProtocol = FileDownloadManager.shared, permissionManager: PermissionManagerProtocol = PermissionManager.shared, geolocationService: GeolocationServiceProtocol = GeolocationService.shared, @@ -303,6 +306,7 @@ protocol NewWindowPolicyDecisionMaker { ?? (NSApp.runType.requiresEnvironment ? DuckPlayer.shared : DuckPlayer.mock(withMode: .enabled)) let statisticsLoader = statisticsLoader ?? (NSApp.runType.requiresEnvironment ? StatisticsLoader.shared : nil) + let onboardingManager: OnboardingManager = onboardingManager ?? OnboardingManager() let privacyFeatures = privacyFeatures ?? PrivacyFeatures let internalUserDecider = NSApp.delegateTyped.internalUserDecider var faviconManager = faviconManagement @@ -310,6 +314,7 @@ protocol NewWindowPolicyDecisionMaker { faviconManager = FaviconManager(cacheType: .inMemory) } + self.init(content: content, faviconManagement: faviconManager, webCacheManager: webCacheManager, @@ -319,6 +324,7 @@ protocol NewWindowPolicyDecisionMaker { workspace: workspace, privacyFeatures: privacyFeatures, duckPlayer: duckPlayer, + onboardingManager: onboardingManager, downloadManager: downloadManager, permissionManager: permissionManager, geolocationService: geolocationService, @@ -349,6 +355,7 @@ protocol NewWindowPolicyDecisionMaker { workspace: Workspace, privacyFeatures: AnyPrivacyFeatures, duckPlayer: DuckPlayer, + onboardingManager: OnboardingManager, downloadManager: FileDownloadManagerProtocol, permissionManager: PermissionManagerProtocol, geolocationService: GeolocationServiceProtocol, @@ -421,7 +428,8 @@ protocol NewWindowPolicyDecisionMaker { workspace: workspace, cbaTimeReporter: cbaTimeReporter, duckPlayer: duckPlayer, - downloadManager: downloadManager)) + downloadManager: downloadManager, + onboardingManager: onboardingManager)) super.init() tabGetter = { [weak self] in self } diff --git a/DuckDuckGo/Tab/TabExtensions/OnboardingTabExtension.swift b/DuckDuckGo/Tab/TabExtensions/OnboardingTabExtension.swift new file mode 100644 index 0000000000..90a2785a14 --- /dev/null +++ b/DuckDuckGo/Tab/TabExtensions/OnboardingTabExtension.swift @@ -0,0 +1,60 @@ +// +// OnboardingTabExtension.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 Combine +import Common +import Foundation +import Navigation + +final class OnboardingTabExtension { + + let onboardingManager: OnboardingManager + + init(onboardingManager: OnboardingManager) { + self.onboardingManager = onboardingManager + } +} + +extension OnboardingTabExtension: NavigationResponder { + + @MainActor + func decidePolicy(for navigationAction: NavigationAction, preferences: inout NavigationPreferences) async -> NavigationActionPolicy? { + // Always allow loading Privacy Debug Tools URLs (from CCF) + if navigationAction.url.isOnboardingScheme { + return .allow + } + return .next + } + + @MainActor + func navigationDidFinish(_ navigation: Navigation) { +// setUpYoutubeScriptsIfNeeded(for: navigation.url) + } + +} + +protocol OnboardingTabExtensionProtocol: AnyObject, NavigationResponder { +} + +extension OnboardingTabExtension: OnboardingTabExtensionProtocol, TabExtension { + func getPublicProtocol() -> OnboardingTabExtensionProtocol { self } +} + +extension TabExtensions { + var onboarding: OnboardingTabExtensionProtocol? { resolve(OnboardingTabExtension.self) } +} diff --git a/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift b/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift index c77a027753..125b57f2ea 100644 --- a/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift +++ b/DuckDuckGo/Tab/TabExtensions/TabExtensions.swift @@ -69,6 +69,7 @@ protocol TabExtensionDependencies { var downloadManager: FileDownloadManagerProtocol { get } var cbaTimeReporter: ContentBlockingAssetsCompilationTimeReporter? { get } var duckPlayer: DuckPlayer { get } + var onboardingManager: OnboardingManager { get } } // swiftlint:disable:next large_tuple @@ -178,6 +179,10 @@ extension TabExtensionsBuilder { scriptsPublisher: userScripts.compactMap { $0 }, webViewPublisher: args.webViewFuture) } + + add { + OnboardingTabExtension(onboardingManager: dependencies.onboardingManager) + } } } diff --git a/DuckDuckGo/Tab/UserScripts/UserScripts.swift b/DuckDuckGo/Tab/UserScripts/UserScripts.swift index 456cddec71..87a15f3a8f 100644 --- a/DuckDuckGo/Tab/UserScripts/UserScripts.swift +++ b/DuckDuckGo/Tab/UserScripts/UserScripts.swift @@ -44,6 +44,7 @@ final class UserScripts: UserScriptsProvider { let autoconsentUserScript: UserScriptWithAutoconsent let youtubeOverlayScript: YoutubeOverlayUserScript? let youtubePlayerUserScript: YoutubePlayerUserScript? + let onboardingUserScript = OnboardingUserScript() init(with sourceProvider: ScriptSourceProviding) { clickToLoadScript = ClickToLoadUserScript(scriptSourceProvider: sourceProvider) @@ -78,6 +79,10 @@ final class UserScripts: UserScriptsProvider { contentScopeUserScriptIsolated.registerSubfeature(delegate: youtubeOverlayScript) } + if let specialPages { + specialPages.registerSubfeature(delegate: onboardingUserScript) + } + if let youtubePlayerUserScript { if let specialPages = specialPages { specialPages.registerSubfeature(delegate: youtubePlayerUserScript) diff --git a/DuckDuckGo/YoutubePlayer/YoutubePlayerNavigationHandler.swift b/DuckDuckGo/YoutubePlayer/YoutubePlayerNavigationHandler.swift index 6c62c9df63..ed4e313518 100644 --- a/DuckDuckGo/YoutubePlayer/YoutubePlayerNavigationHandler.swift +++ b/DuckDuckGo/YoutubePlayer/YoutubePlayerNavigationHandler.swift @@ -22,7 +22,7 @@ import ContentScopeScripts struct YoutubePlayerNavigationHandler { static var htmlTemplatePath: String { - guard let file = ContentScopeScripts.Bundle.path(forResource: "index", ofType: "html", inDirectory: "pages/duckplayer") else { + guard let file = ContentScopeScripts.Bundle.path(forResource: "index", ofType: "html", inDirectory: "pages/onboarding") else { assertionFailure("YouTube Private Player HTML template not found") return "" }