From 471a5eb7cfe019900ab5dae06d72f6db1910b443 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 4 Jul 2024 15:08:02 +0200 Subject: [PATCH 01/37] First batch of pixels --- Core/PixelEvent.swift | 21 ++++++++++++++++ .../YouTubePlayerNavigationHandler.swift | 9 +++++++ .../DuckPlayer/YoutubeOverlayUserScript.swift | 25 +++++++++++++++---- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 4e48109b4f..61d2493d16 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -720,6 +720,27 @@ extension Pixel { case favoriteLaunchedNTPDaily case bookmarkLaunchedDaily case newTabPageDisplayedDaily + + // MARK: DuckPlayer + case duckPlayerDailyUniqueView + case duckPlayerViewFromYoutubeViaMainOverlay + case duckPlayerViewFromYoutubeViaHoverButton + case duckPlayerViewFromYoutubeAutomatic + case duckPlayerViewFromSERP + case duckPlayerViewFromOther + case duckPlayerOverlayYoutubeImpressions + case duckPlayerOverlayYoutubeWatchHere + case duckPlayerSettingAlwaysDuckPlayer + case duckPlayerSettingAlwaysOverlaySERP + case duckPlayerSettingAlwaysOverlayYoutube + case duckPlayerSettingAlwaysSettings + case duckPlayerSettingNeverOverlaySERP + case duckPlayerSettingNeverOverlayYoutube + case duckPlayerSettingNeverSettings + case duckPlayerSettingBackToDefault + case duckPlayerWatchOnYoutube + case watchInDuckPlayerInitial + } } diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index e38bb6a728..efe099d130 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -20,6 +20,7 @@ import Foundation import ContentScopeScripts import WebKit +import Core final class YoutubePlayerNavigationHandler { @@ -87,6 +88,14 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { webView: WKWebView, completion: @escaping (WKNavigationActionPolicy) -> Void) { + // Daily Unique View Pixel + if let url = navigationAction.request.url, + url.isDuckPlayer, + duckPlayer.settings.mode != .disabled { + let setting = duckPlayer.settings.mode == .enabled ? "always" : "default" + DailyPixel.fire(pixel: Pixel.Event.duckPlayerDailyUniqueView, withAdditionalParameters: ["setting": setting]) + } + // If DuckPlayer is Enabled or in ask mode, render the video if let url = navigationAction.request.url, url.isDuckURLScheme, diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index f7228f11a7..494df9d319 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -22,6 +22,7 @@ import WebKit import Common import UserScript import Combine +import Core final class YoutubeOverlayUserScript: NSObject, Subfeature { @@ -143,11 +144,25 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { extension YoutubeOverlayUserScript { @MainActor func handleSendJSPixel(params: Any, message: UserScriptMessage) -> Encodable? { - // guard let body = message.messageBody as? [String: Any], let parameters = body["params"] as? [String: Any] else { - // return nil - // } - // let pixelName = parameters["pixelName"] as? String - // To be implemented at a later point + guard let body = message.messageBody as? [String: Any], let parameters = body["params"] as? [String: Any] else { + return nil + } + let pixelName = parameters["pixelName"] as? String + + switch pixelName { + case "play.use": + Pixel.fire(Pixel.Event.duckPlayerViewFromYoutubeViaMainOverlay) + UniquePixel.fire(Pixel.Event.watchInDuckPlayerInitial) + + case "play.do_not_use": + Pixel.fire(Pixel.Event.duckPlayerOverlayYoutubeWatchHere) + + case "overlay": + Pixel.fire(Pixel.Event.duckPlayerOverlayYoutubeImpressions) + + default: + break + } return nil } From b4f5626c662d2af8b78a2b79425cc2539f64b22a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 9 Jul 2024 17:49:28 +0200 Subject: [PATCH 02/37] Watch From SERP pixel --- Core/PixelEvent.swift | 20 +++++++++++ .../YouTubePlayerNavigationHandler.swift | 35 ++++++++++++++----- .../DuckPlayer/YoutubeOverlayUserScript.swift | 8 ++--- 3 files changed, 51 insertions(+), 12 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 61d2493d16..a55882250c 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -1450,6 +1450,26 @@ extension Pixel.Event { case .favoriteLaunchedNTPDaily: return "m_favorite_launched_ntp_daily" case .bookmarkLaunchedDaily: return "m_bookmark_launched_daily" case .newTabPageDisplayedDaily: return "m_new_tab_page_displayed_daily" + + // MARK: DuckPlayer + case .duckPlayerDailyUniqueView: return "m_duck-player_daily-unique-view" + case .duckPlayerViewFromYoutubeViaMainOverlay: return "m_duck-player_view-from_youtube_main-overlay" + case .duckPlayerViewFromYoutubeViaHoverButton: return "m_duck-player_view-from_youtube_hover-button" + case .duckPlayerViewFromYoutubeAutomatic: return "m_duck-player_view-from_youtube_automatic" + case .duckPlayerViewFromSERP: return "m_duck-player_view-from_serp" + case .duckPlayerViewFromOther: return "m_duck-player_view-from_other" + case .duckPlayerSettingAlwaysSettings: return "m_duck-player_setting_always_settings" + case .duckPlayerOverlayYoutubeImpressions: return "m_duck-player_overlay_youtube_impressions" + case .duckPlayerOverlayYoutubeWatchHere: return "m_duck-player_overlay_youtube_watch_here" + case .duckPlayerSettingAlwaysDuckPlayer: return "m_duck-player_setting_always_duck-player" + case .duckPlayerSettingAlwaysOverlaySERP: return "m_duck-player_setting_always_overlay_serp" + case .duckPlayerSettingAlwaysOverlayYoutube: return "m_duck-player_setting_always_overlay_youtube" + case .duckPlayerSettingNeverOverlaySERP: return "m_duck-player_setting_never_overlay_serp" + case .duckPlayerSettingNeverOverlayYoutube: return "m_duck-player_setting_never_overlay_youtube" + case .duckPlayerSettingNeverSettings: return "m_duck-player_setting_never_settings" + case .duckPlayerSettingBackToDefault: return "m_duck-player_setting_back-to-default" + case .duckPlayerWatchOnYoutube: return "m_duck-player_watch_on_youtube" + case .watchInDuckPlayerInitial: return "m_watch-in-duckplayer_initial" } } } diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index efe099d130..2c468967a5 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -26,15 +26,25 @@ final class YoutubePlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol + private struct Constants { + static let SERPURL = "https://duckduckgo.com/" + static let refererHeader = "Referer" + static let templateDirectory = "pages/duckplayer" + static let templateName = "index" + static let templateExtension = "html" + static let localhost = "http://localhost" + static let duckPlayerAlwaysString = "always" + static let duckPlayerDefaultString = "default" + static let settingsKey = "settings" + static let httpMethod = "GET" + } + init(duckPlayer: DuckPlayerProtocol) { self.duckPlayer = duckPlayer } - private static let templateDirectory = "pages/duckplayer" - private static let templateName = "index" - static var htmlTemplatePath: String { - guard let file = ContentScopeScripts.Bundle.path(forResource: Self.templateName, ofType: "html", inDirectory: Self.templateDirectory) else { + guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, ofType: Constants.templateExtension, inDirectory: Constants.templateDirectory) else { assertionFailure("YouTube Private Player HTML template not found") return "" } @@ -51,8 +61,8 @@ final class YoutubePlayerNavigationHandler { static func makeDuckPlayerRequest(for videoID: String, timestamp: String?) -> URLRequest { var request = URLRequest(url: .youtubeNoCookie(videoID, timestamp: timestamp)) - request.addValue("http://localhost/", forHTTPHeaderField: "Referer") - request.httpMethod = "GET" + request.addValue(Constants.localhost, forHTTPHeaderField: Constants.refererHeader) + request.httpMethod = Constants.httpMethod return request } @@ -92,8 +102,8 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { if let url = navigationAction.request.url, url.isDuckPlayer, duckPlayer.settings.mode != .disabled { - let setting = duckPlayer.settings.mode == .enabled ? "always" : "default" - DailyPixel.fire(pixel: Pixel.Event.duckPlayerDailyUniqueView, withAdditionalParameters: ["setting": setting]) + let setting = duckPlayer.settings.mode == .enabled ? Constants.duckPlayerAlwaysString : Constants.duckPlayerDefaultString + DailyPixel.fire(pixel: Pixel.Event.duckPlayerDailyUniqueView, withAdditionalParameters: [Constants.settingsKey: setting]) } // If DuckPlayer is Enabled or in ask mode, render the video @@ -142,6 +152,15 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, completion: @escaping (WKNavigationActionPolicy) -> Void, webView: WKWebView) { + + // Pixel for Views From SERP + if let url = navigationAction.request.url, + navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] == Constants.SERPURL, + duckPlayer.settings.mode == .enabled, !url.isDuckPlayer { + Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromSERP, debounce: 2) + } + + if let url = navigationAction.request.url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index 494df9d319..192920b486 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -151,14 +151,14 @@ extension YoutubeOverlayUserScript { switch pixelName { case "play.use": - Pixel.fire(Pixel.Event.duckPlayerViewFromYoutubeViaMainOverlay) - UniquePixel.fire(Pixel.Event.watchInDuckPlayerInitial) + Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeViaMainOverlay) + UniquePixel.fire(pixel: Pixel.Event.watchInDuckPlayerInitial) case "play.do_not_use": - Pixel.fire(Pixel.Event.duckPlayerOverlayYoutubeWatchHere) + Pixel.fire(pixel: Pixel.Event.duckPlayerOverlayYoutubeWatchHere) case "overlay": - Pixel.fire(Pixel.Event.duckPlayerOverlayYoutubeImpressions) + Pixel.fire(pixel: Pixel.Event.duckPlayerOverlayYoutubeImpressions) default: break From 70c739ed479ef4d15194a0d6cbdd90703cee5fbb Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 9 Jul 2024 18:04:36 +0200 Subject: [PATCH 03/37] Update package --- .../xcshareddata/swiftpm/Package.resolved | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 804d062cbb..504b9594fb 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -138,10 +138,10 @@ { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", + "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", - "version" : "1.4.0" + "revision" : "46989693916f56d1186bd59ac15124caef896560", + "version" : "1.3.1" } }, { From 5dbb45aa8a0bf2cc219df681bf52f8e6cee41bdb Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 9 Jul 2024 18:31:40 +0200 Subject: [PATCH 04/37] Test fix --- DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift index 32fc0565ae..fe4f76eb09 100644 --- a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift +++ b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift @@ -67,7 +67,7 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { XCTAssertEqual(duckPlayerRequest.url?.host, "www.youtube-nocookie.com") XCTAssertEqual(duckPlayerRequest.url?.path, "/embed/abc123") XCTAssertEqual(duckPlayerRequest.url?.query?.contains("t=10s"), true) - XCTAssertEqual(duckPlayerRequest.value(forHTTPHeaderField: "Referer"), "http://localhost/") + XCTAssertEqual(duckPlayerRequest.value(forHTTPHeaderField: "Referer"), "http://localhost") XCTAssertEqual(duckPlayerRequest.httpMethod, "GET") } @@ -81,7 +81,7 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { XCTAssertEqual(duckPlayerRequest.url?.host, "www.youtube-nocookie.com") XCTAssertEqual(duckPlayerRequest.url?.path, "/embed/abc123") XCTAssertEqual(duckPlayerRequest.url?.query?.contains("t=10s"), true) - XCTAssertEqual(duckPlayerRequest.value(forHTTPHeaderField: "Referer"), "http://localhost/") + XCTAssertEqual(duckPlayerRequest.value(forHTTPHeaderField: "Referer"), "http://localhost") XCTAssertEqual(duckPlayerRequest.httpMethod, "GET") } From 593d75510ece567f841ab73e2b8e25667b3848f2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 9 Jul 2024 18:49:17 +0200 Subject: [PATCH 05/37] Fix lint issue --- DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index 2c468967a5..aaa1d13497 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -44,7 +44,9 @@ final class YoutubePlayerNavigationHandler { } static var htmlTemplatePath: String { - guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, ofType: Constants.templateExtension, inDirectory: Constants.templateDirectory) else { + guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, + ofType: Constants.templateExtension, + inDirectory: Constants.templateDirectory) else { assertionFailure("YouTube Private Player HTML template not found") return "" } From a8141ce094562c1d4206345e89c6d6ab16f8dd26 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 13:34:41 +0200 Subject: [PATCH 06/37] Add first launch pixel --- Core/PixelEvent.swift | 2 +- .../DuckPlayer/YoutubeOverlayUserScript.swift | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Core/PixelEvent.swift b/Core/PixelEvent.swift index 4c75976958..287d060c67 100644 --- a/Core/PixelEvent.swift +++ b/Core/PixelEvent.swift @@ -1468,7 +1468,7 @@ extension Pixel.Event { case .duckPlayerSettingNeverSettings: return "m_duck-player_setting_never_settings" case .duckPlayerSettingBackToDefault: return "m_duck-player_setting_back-to-default" case .duckPlayerWatchOnYoutube: return "m_duck-player_watch_on_youtube" - case .watchInDuckPlayerInitial: return "m_watch-in-duckplayer_initial" + case .watchInDuckPlayerInitial: return "m_watch-in-duckplayer_initial_u" } } } diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index 192920b486..091afb731b 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -23,18 +23,21 @@ import Common import UserScript import Combine import Core +import BrowserServicesKit final class YoutubeOverlayUserScript: NSObject, Subfeature { var duckPlayer: DuckPlayerProtocol private var cancellables = Set() + var statisticsStore: StatisticsStore struct Constants { static let featureName = "duckPlayer" } - init(duckPlayer: DuckPlayerProtocol) { + init(duckPlayer: DuckPlayerProtocol, statisticsStore: StatisticsStore = StatisticsUserDefaults()) { self.duckPlayer = duckPlayer + self.statisticsStore = statisticsStore super.init() subscribeToDuckPlayerMode() } @@ -72,6 +75,7 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { static let getUserValues = "getUserValues" static let openDuckPlayer = "openDuckPlayer" static let sendDuckPlayerPixel = "sendDuckPlayerPixel" + static let initialSetup = "initialSetup" } weak var broker: UserScriptMessageBroker? @@ -102,6 +106,8 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { return openDuckPlayer case Handlers.sendDuckPlayerPixel: return handleSendJSPixel + case Handlers.initialSetup: + return duckPlayer.initialSetup default: assertionFailure("YoutubeOverlayUserScript: Failed to parse User Script message: \(methodName)") // TODO: Send pixel here @@ -152,7 +158,11 @@ extension YoutubeOverlayUserScript { switch pixelName { case "play.use": Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeViaMainOverlay) - UniquePixel.fire(pixel: Pixel.Event.watchInDuckPlayerInitial) + + if let installDate = statisticsStore.installDate, + installDate > Date.yearAgo { + UniquePixel.fire(pixel: Pixel.Event.watchInDuckPlayerInitial) + } case "play.do_not_use": Pixel.fire(pixel: Pixel.Event.duckPlayerOverlayYoutubeWatchHere) From 29d5d3adc628a526c8a4ca5207a036e1c5b36b25 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 15:18:26 +0200 Subject: [PATCH 07/37] Add Settings Pixels --- DuckDuckGo/SettingsViewModel.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 74a4202ed2..988e4bccd1 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -260,6 +260,17 @@ final class SettingsViewModel: ObservableObject { set: { self.appSettings.duckPlayerMode = $0 self.state.duckPlayerMode = $0 + + switch self.state.duckPlayerMode { + case .alwaysAsk: + Pixel.fire(pixel: Pixel.Event.duckPlayerSettingBackToDefault) + case .disabled: + Pixel.fire(pixel: Pixel.Event.duckPlayerSettingNeverSettings) + case .enabled: + Pixel.fire(pixel: Pixel.Event.duckPlayerSettingNeverSettings) + default: + break + } } ) } From 16d3f98750848d056cab1f44e05fd94da1140cf3 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 15:20:05 +0200 Subject: [PATCH 08/37] Settings Pixels --- DuckDuckGo/SettingsViewModel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index 988e4bccd1..9f215b0121 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -267,7 +267,7 @@ final class SettingsViewModel: ObservableObject { case .disabled: Pixel.fire(pixel: Pixel.Event.duckPlayerSettingNeverSettings) case .enabled: - Pixel.fire(pixel: Pixel.Event.duckPlayerSettingNeverSettings) + Pixel.fire(pixel: Pixel.Event.duckPlayerSettingAlwaysSettings) default: break } From e4628662d335ab37b4fa0e875a8d8fc38a0efee3 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 15:23:38 +0200 Subject: [PATCH 09/37] DuckPlayer from non-SERP --- DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index aaa1d13497..fb49d78cbf 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -162,6 +162,13 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromSERP, debounce: 2) } + // Pixel for views from Other Sites + if let url = navigationAction.request.url, + navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] != Constants.SERPURL, + duckPlayer.settings.mode == .enabled, !url.isDuckPlayer { + Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromOther, debounce: 2) + } + if let url = navigationAction.request.url, url.isYoutubeVideo, From 239c8c043a0b2d9743125e2fe0f8e3a79b3d6e2c Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 16:08:34 +0200 Subject: [PATCH 10/37] duckPlayerViewFromYoutubeAutomatic --- DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift | 1 + DuckDuckGo/DuckPlayer/DuckPlayer.swift | 5 ++++- DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift | 6 ++++++ .../YouTubePlayerNavigationHandler.swift | 14 ++++++++++++++ DuckDuckGo/TabViewController.swift | 6 ++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift b/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift index 809889a797..c2e2178472 100644 --- a/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift +++ b/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift @@ -20,6 +20,7 @@ import WebKit protocol DuckNavigationHandling { + var referrer: DuckPlayerReferrer { get set } func handleNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView, completion: @escaping (WKNavigationActionPolicy) -> Void) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index c883848a5b..fb54fd00b9 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -54,6 +54,10 @@ public struct UserValues: Codable { let askModeOverlayHidden: Bool } +public enum DuckPlayerReferrer { + case youtube, other +} + protocol DuckPlayerProtocol { var settings: DuckPlayerSettingsProtocol { get } @@ -66,7 +70,6 @@ protocol DuckPlayerProtocol { func initialSetup(params: Any, message: WKScriptMessage) async -> Encodable? } - final class DuckPlayer: DuckPlayerProtocol { static let duckPlayerHost: String = "player" diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift b/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift index 96f1b0eaeb..d17a9b3780 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift @@ -112,6 +112,12 @@ extension URL { } + var isYoutube: Bool { + guard let host else { return false } + return host == "m.youtube.com" || host == "youtube.com" + + } + private func addingTimestamp(_ timestamp: String?) -> URL { guard let timestamp = timestamp, let regex = try? NSRegularExpression(pattern: "^(\\d+[smh]?)+$"), diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index fb49d78cbf..d28415eec7 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -25,6 +25,11 @@ import Core final class YoutubePlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol + var referrer: DuckPlayerReferrer = .other { + didSet { + print(referrer) + } + } private struct Constants { static let SERPURL = "https://duckduckgo.com/" @@ -108,6 +113,13 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { DailyPixel.fire(pixel: Pixel.Event.duckPlayerDailyUniqueView, withAdditionalParameters: [Constants.settingsKey: setting]) } + // Pixel for Views From Youtube + if let url = navigationAction.request.url, + referrer == .youtube, + duckPlayer.settings.mode == .enabled { + Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeAutomatic, debounce: 2) + } + // If DuckPlayer is Enabled or in ask mode, render the video if let url = navigationAction.request.url, url.isDuckURLScheme, @@ -138,6 +150,7 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { // such as changes triggered via JS @MainActor func handleURLChange(url: URL?, webView: WKWebView) { + if let url = url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, @@ -146,6 +159,7 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { let newURL = URL.duckPlayer(videoID, timestamp: timestamp) webView.load(URLRequest(url: newURL)) } + } // DecidePolicyFor handler to redirect relevant requests diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 07bab32c00..65eaa0b326 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -666,6 +666,12 @@ class TabViewController: UIViewController { handler.handleURLChange(url: url, webView: webView) } } + + if var handler = youtubeNavigationHandler, + let url { + handler.referrer = url.isYoutube ? .youtube : .other + + } } func enableFireproofingForDomain(_ domain: String) { From f41a482a2f80406ca10d532950fa28895e403531 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 17:26:28 +0200 Subject: [PATCH 11/37] Initial Setup Updates --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 52 ++++++++++++++++--- .../DuckPlayer/YoutubeOverlayUserScript.swift | 2 +- .../DuckPlayer/YoutubePlayerUserScript.swift | 2 +- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index fb54fd00b9..3df84a9b4b 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -34,16 +34,33 @@ struct InitialSetupSettings: Codable { struct PIP: Codable { let status: Status } + + enum Platform: String, Codable { + case ios + case macos + } enum Status: String, Codable { case enabled case disabled } + + enum Environment: String, Codable { + case development + case production + } + + enum Locale: String, Codable { + case en + } let userValues: UserValues let settings: PlayerSettings + let platform: Platform + let locale: Locale } + /// Values that the Frontend can use to determine user settings public struct UserValues: Codable { enum CodingKeys: String, CodingKey { @@ -67,7 +84,9 @@ protocol DuckPlayerProtocol { func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? func getUserValues(params: Any, message: WKScriptMessage) -> Encodable? func openVideoInDuckPlayer(url: URL, webView: WKWebView) - func initialSetup(params: Any, message: WKScriptMessage) async -> Encodable? + + func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? + func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? } final class DuckPlayer: DuckPlayerProtocol { @@ -103,9 +122,15 @@ final class DuckPlayer: DuckPlayerProtocol { } @MainActor - public func initialSetup(params: Any, message: WKScriptMessage) async -> Encodable? { + public func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? { let webView = message.webView - return await self.encodedSettings(with: webView) + return await self.encodedPlayerSettings(with: webView) + } + + @MainActor + public func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? { + let webView = message.webView + return await self.encodedPlayerSettings(with: webView) } private func encodeUserValues() -> UserValues { @@ -116,14 +141,27 @@ final class DuckPlayer: DuckPlayerProtocol { } @MainActor - private func encodedSettings(with webView: WKWebView?) async -> InitialSetupSettings { + private func encodedPlayerSettings(with webView: WKWebView?) async -> InitialSetupSettings { let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true let pip = InitialSetupSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) - + let platform = InitialSetupSettings.Platform.ios + let environment = InitialSetupSettings.Environment.development + let locale = InitialSetupSettings.Locale.en let playerSettings = InitialSetupSettings.PlayerSettings(pip: pip) let userValues = encodeUserValues() - - return InitialSetupSettings(userValues: userValues, settings: playerSettings) + return InitialSetupSettings(userValues: userValues, settings: playerSettings, platform: platform, locale: locale) + } + + @MainActor + private func encodedOverlaySettings(with webView: WKWebView?) async -> InitialSetupSettings { + let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true + let pip = InitialSetupSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) + let platform = InitialSetupSettings.Platform.ios + let environment = InitialSetupSettings.Environment.development + let locale = InitialSetupSettings.Locale.en + let playerSettings = InitialSetupSettings.PlayerSettings(pip: pip) + let userValues = encodeUserValues() + return InitialSetupSettings(userValues: userValues, settings: playerSettings, platform: platform, locale: locale) } } diff --git a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift index 091afb731b..261e1d912b 100644 --- a/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubeOverlayUserScript.swift @@ -107,7 +107,7 @@ final class YoutubeOverlayUserScript: NSObject, Subfeature { case Handlers.sendDuckPlayerPixel: return handleSendJSPixel case Handlers.initialSetup: - return duckPlayer.initialSetup + return duckPlayer.initialSetupOverlay default: assertionFailure("YoutubeOverlayUserScript: Failed to parse User Script message: \(methodName)") // TODO: Send pixel here diff --git a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift index 2536de5f83..af283c3615 100644 --- a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift @@ -72,7 +72,7 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { case Handlers.setUserValues: return duckPlayer.setUserValues case Handlers.initialSetup: - return duckPlayer.initialSetup + return duckPlayer.initialSetupPlayer default: assertionFailure("YoutubePlayerUserScript: Failed to parse User Script message: \(methodName)") return nil From 310be79a0960133e681588c346cde8add3fda053 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 17:29:03 +0200 Subject: [PATCH 12/37] Settings --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 3df84a9b4b..094844a047 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -35,9 +35,8 @@ struct InitialSetupSettings: Codable { let status: Status } - enum Platform: String, Codable { - case ios - case macos + struct Platform: Codable { + let name: String } enum Status: String, Codable { @@ -144,7 +143,7 @@ final class DuckPlayer: DuckPlayerProtocol { private func encodedPlayerSettings(with webView: WKWebView?) async -> InitialSetupSettings { let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true let pip = InitialSetupSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) - let platform = InitialSetupSettings.Platform.ios + let platform = InitialSetupSettings.Platform(name: "ios") let environment = InitialSetupSettings.Environment.development let locale = InitialSetupSettings.Locale.en let playerSettings = InitialSetupSettings.PlayerSettings(pip: pip) @@ -156,7 +155,7 @@ final class DuckPlayer: DuckPlayerProtocol { private func encodedOverlaySettings(with webView: WKWebView?) async -> InitialSetupSettings { let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true let pip = InitialSetupSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) - let platform = InitialSetupSettings.Platform.ios + let platform = InitialSetupSettings.Platform(name: "ios") let environment = InitialSetupSettings.Environment.development let locale = InitialSetupSettings.Locale.en let playerSettings = InitialSetupSettings.PlayerSettings(pip: pip) From d21ce8b9cb808e0e7a78a61eceb3506944b49a3d Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 17:41:23 +0200 Subject: [PATCH 13/37] Split Overlay and Player Settings --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 29 +++++++++++++------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 094844a047..8d18f090f8 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -26,7 +26,7 @@ import UserScript import Core /// Values that the Frontend can use to determine the current state. -struct InitialSetupSettings: Codable { +struct InitialPlayerSettings: Codable { struct PlayerSettings: Codable { let pip: PIP } @@ -59,6 +59,10 @@ struct InitialSetupSettings: Codable { let locale: Locale } +struct InitialOverlaySettings: Codable { + let userValues: UserValues +} + /// Values that the Frontend can use to determine user settings public struct UserValues: Codable { @@ -140,27 +144,22 @@ final class DuckPlayer: DuckPlayerProtocol { } @MainActor - private func encodedPlayerSettings(with webView: WKWebView?) async -> InitialSetupSettings { + private func encodedPlayerSettings(with webView: WKWebView?) async -> InitialPlayerSettings { let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true - let pip = InitialSetupSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) - let platform = InitialSetupSettings.Platform(name: "ios") - let environment = InitialSetupSettings.Environment.development - let locale = InitialSetupSettings.Locale.en - let playerSettings = InitialSetupSettings.PlayerSettings(pip: pip) + let pip = InitialPlayerSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) + let platform = InitialPlayerSettings.Platform(name: "ios") + let environment = InitialPlayerSettings.Environment.development + let locale = InitialPlayerSettings.Locale.en + let playerSettings = InitialPlayerSettings.PlayerSettings(pip: pip) let userValues = encodeUserValues() - return InitialSetupSettings(userValues: userValues, settings: playerSettings, platform: platform, locale: locale) + return InitialPlayerSettings(userValues: userValues, settings: playerSettings, platform: platform, locale: locale) } @MainActor - private func encodedOverlaySettings(with webView: WKWebView?) async -> InitialSetupSettings { + private func encodedOverlaySettings(with webView: WKWebView?) async -> InitialOverlaySettings { let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true - let pip = InitialSetupSettings.PIP(status: isPiPEnabled ? .enabled : .disabled) - let platform = InitialSetupSettings.Platform(name: "ios") - let environment = InitialSetupSettings.Environment.development - let locale = InitialSetupSettings.Locale.en - let playerSettings = InitialSetupSettings.PlayerSettings(pip: pip) let userValues = encodeUserValues() - return InitialSetupSettings(userValues: userValues, settings: playerSettings, platform: platform, locale: locale) + return InitialOverlaySettings(userValues: userValues) } } From 7c59947c67b6afc57c2af15823fa2901e4ce45c9 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 17:42:01 +0200 Subject: [PATCH 14/37] Remove PiP Settings --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 8d18f090f8..df7e5ecf29 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -157,7 +157,6 @@ final class DuckPlayer: DuckPlayerProtocol { @MainActor private func encodedOverlaySettings(with webView: WKWebView?) async -> InitialOverlaySettings { - let isPiPEnabled = webView?.configuration.allowsPictureInPictureMediaPlayback == true let userValues = encodeUserValues() return InitialOverlaySettings(userValues: userValues) } From a47c7bece4eb445e38e64fd5e97158e56f38c0d2 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 10 Jul 2024 18:18:41 +0200 Subject: [PATCH 15/37] Packages --- .../xcshareddata/swiftpm/Package.resolved | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index c6a1e62e6d..6a4c6b33bc 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,42 @@ "version" : "3.1.1" } }, + { + "identity" : "barebonesbrowser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", + "state" : { + "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", + "version" : "0.1.0" + } + }, + { + "identity" : "bloom_cpp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/bloom_cpp.git", + "state" : { + "revision" : "8076199456290b61b4544bf2f4caf296759906a0", + "version" : "3.0.0" + } + }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", + "state" : { + "revision" : "522a9defa0eb951c9101d9f54c19d51f8ef57f4c", + "version" : "169.1.0" + } + }, + { + "identity" : "content-scope-scripts", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/content-scope-scripts", + "state" : { + "revision" : "9c65477457126ab7ad963a32b7f85ce08e6bd1a7", + "version" : "6.0.0" + } + }, { "identity" : "designresourceskit", "kind" : "remoteSourceControl", @@ -18,6 +54,87 @@ "version" : "3.0.0" } }, + { + "identity" : "duckduckgo-autofill", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state" : { + "revision" : "2b81745565db09eee8c1cd44d38eefa1011a9f0a", + "version" : "12.0.1" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/GRDB.swift.git", + "state" : { + "revision" : "9f049d7b97b1e68ffd86744b500660d34a9e79b8", + "version" : "2.3.0" + } + }, + { + "identity" : "gzipswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/1024jp/GzipSwift.git", + "state" : { + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" + } + }, + { + "identity" : "ios-js-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/ios-js-support", + "state" : { + "revision" : "6a6789ac8104a587316c58af75539753853b50d9", + "version" : "2.0.0" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", + "version" : "7.12.0" + } + }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", + "version" : "4.4.3" + } + }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "privacy-dashboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/privacy-dashboard", + "state" : { + "revision" : "348594efe2cd40ef156e915c272d02ec22f1903f", + "version" : "4.2.0" + } + }, + { + "identity" : "punycodeswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gumob/PunycodeSwift.git", + "state" : { + "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b", + "version" : "2.1.0" + } + }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", @@ -35,6 +152,60 @@ "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", "version" : "509.1.1" } + }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter.git", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup", + "state" : { + "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", + "version" : "2.7.2" + } + }, + { + "identity" : "sync_crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/sync_crypto", + "state" : { + "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version" : "0.2.0" + } + }, + { + "identity" : "trackerradarkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "state" : { + "revision" : "1403e17eeeb8493b92fb9d11eb8c846bb9776581", + "version" : "2.1.2" + } + }, + { + "identity" : "wireguard-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/wireguard-apple", + "state" : { + "revision" : "13fd026384b1af11048451061cc1b21434990668", + "version" : "1.1.3" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" + } } ], "version" : 2 From ff9e04f8068e5b00f43c211598a366dcf60abd62 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 16 Jul 2024 15:49:13 +0200 Subject: [PATCH 16/37] Handle Watch in Youtube Action --- Core/Logging.swift | 5 +- DuckDuckGo.xcodeproj/project.pbxproj | 2 + .../xcshareddata/swiftpm/Package.resolved | 413 +++++++++--------- .../YouTubePlayerNavigationHandler.swift | 65 ++- 4 files changed, 260 insertions(+), 225 deletions(-) diff --git a/Core/Logging.swift b/Core/Logging.swift index fd6bdac037..9be20c9153 100644 --- a/Core/Logging.swift +++ b/Core/Logging.swift @@ -31,6 +31,7 @@ public extension OSLog { case autoconsentLog = "DDG Autoconsent" case configurationLog = "DDG Configuration" case syncLog = "DDG Sync" + case duckPlayerLog = "Duck Player" } @OSLogWrapper(.generalLog) static var generalLog @@ -40,6 +41,7 @@ public extension OSLog { @OSLogWrapper(.autoconsentLog) static var autoconsentLog @OSLogWrapper(.configurationLog) static var configurationLog @OSLogWrapper(.syncLog) static var syncLog + @OSLogWrapper(.duckPlayerLog) static var duckPlayerLog // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // To activate Logging Categories add categories here: @@ -50,7 +52,8 @@ public extension OSLog { .adAttributionLog, .lifecycleLog, .configurationLog, - .syncLog + .syncLog, + .duckPlayerLog ] #endif diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index fcc5249d06..846f0cd142 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2438,6 +2438,7 @@ D62EC3C12C248AF800FC9D04 /* DuckNavigationHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckNavigationHandling.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = ""; }; + D636DDF02C468BA7003696BD /* content-scope-scripts */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "content-scope-scripts"; path = "../content-scope-scripts"; sourceTree = ""; }; D63FF8892C1B21C2006DE24D /* YouTubePlayerNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubePlayerNavigationHandler.swift; sourceTree = ""; }; D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerURLExtension.swift; sourceTree = ""; }; D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubePlayerUserScript.swift; sourceTree = ""; }; @@ -3629,6 +3630,7 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( + D636DDF02C468BA7003696BD /* content-scope-scripts */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 6a4c6b33bc..703b310454 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,212 +1,205 @@ { - "pins" : [ - { - "identity" : "apple-toolbox", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/apple-toolbox.git", - "state" : { - "revision" : "ab53ca41e9044a20eab7e53249526fadcf9acc9f", - "version" : "3.1.1" - } - }, - { - "identity" : "barebonesbrowser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", - "state" : { - "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", - "version" : "0.1.0" - } - }, - { - "identity" : "bloom_cpp", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/bloom_cpp.git", - "state" : { - "revision" : "8076199456290b61b4544bf2f4caf296759906a0", - "version" : "3.0.0" - } - }, - { - "identity" : "browserserviceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", - "state" : { - "revision" : "522a9defa0eb951c9101d9f54c19d51f8ef57f4c", - "version" : "169.1.0" - } - }, - { - "identity" : "content-scope-scripts", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/content-scope-scripts", - "state" : { - "revision" : "9c65477457126ab7ad963a32b7f85ce08e6bd1a7", - "version" : "6.0.0" - } - }, - { - "identity" : "designresourceskit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/DesignResourcesKit", - "state" : { - "revision" : "ae83941bb277a2750abc2d6697fa278f8c8c5f5e", - "version" : "3.0.0" - } - }, - { - "identity" : "duckduckgo-autofill", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", - "state" : { - "revision" : "2b81745565db09eee8c1cd44d38eefa1011a9f0a", - "version" : "12.0.1" - } - }, - { - "identity" : "grdb.swift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/GRDB.swift.git", - "state" : { - "revision" : "9f049d7b97b1e68ffd86744b500660d34a9e79b8", - "version" : "2.3.0" - } - }, - { - "identity" : "gzipswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/1024jp/GzipSwift.git", - "state" : { - "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", - "version" : "6.0.1" - } - }, - { - "identity" : "ios-js-support", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/ios-js-support", - "state" : { - "revision" : "6a6789ac8104a587316c58af75539753853b50d9", - "version" : "2.0.0" - } - }, - { - "identity" : "kingfisher", - "kind" : "remoteSourceControl", - "location" : "https://github.com/onevcat/Kingfisher.git", - "state" : { - "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", - "version" : "7.12.0" - } - }, - { - "identity" : "lottie-spm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/airbnb/lottie-spm.git", - "state" : { - "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", - "version" : "4.4.3" - } - }, - { - "identity" : "ohhttpstubs", - "kind" : "remoteSourceControl", - "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", - "state" : { - "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version" : "9.1.0" - } - }, - { - "identity" : "privacy-dashboard", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/privacy-dashboard", - "state" : { - "revision" : "348594efe2cd40ef156e915c272d02ec22f1903f", - "version" : "4.2.0" - } - }, - { - "identity" : "punycodeswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gumob/PunycodeSwift.git", - "state" : { - "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b", - "version" : "2.1.0" - } - }, - { - "identity" : "swift-argument-parser", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser", - "state" : { - "revision" : "46989693916f56d1186bd59ac15124caef896560", - "version" : "1.3.1" - } - }, - { - "identity" : "swift-syntax", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", - "state" : { - "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", - "version" : "509.1.1" - } - }, - { - "identity" : "swifter", - "kind" : "remoteSourceControl", - "location" : "https://github.com/httpswift/swifter.git", - "state" : { - "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", - "version" : "1.5.0" - } - }, - { - "identity" : "swiftsoup", - "kind" : "remoteSourceControl", - "location" : "https://github.com/scinfu/SwiftSoup", - "state" : { - "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", - "version" : "2.7.2" - } - }, - { - "identity" : "sync_crypto", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/sync_crypto", - "state" : { - "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", - "version" : "0.2.0" - } - }, - { - "identity" : "trackerradarkit", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/TrackerRadarKit", - "state" : { - "revision" : "1403e17eeeb8493b92fb9d11eb8c846bb9776581", - "version" : "2.1.2" - } - }, - { - "identity" : "wireguard-apple", - "kind" : "remoteSourceControl", - "location" : "https://github.com/duckduckgo/wireguard-apple", - "state" : { - "revision" : "13fd026384b1af11048451061cc1b21434990668", - "version" : "1.1.3" - } - }, - { - "identity" : "zipfoundation", - "kind" : "remoteSourceControl", - "location" : "https://github.com/weichsel/ZIPFoundation.git", - "state" : { - "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version" : "0.9.19" - } - } - ], - "version" : 2 + "object": { + "pins": [ + { + "package": "AppleToolbox", + "repositoryURL": "https://github.com/duckduckgo/apple-toolbox.git", + "state": { + "branch": null, + "revision": "ab53ca41e9044a20eab7e53249526fadcf9acc9f", + "version": "3.1.1" + } + }, + { + "package": "BareBonesBrowserKit", + "repositoryURL": "https://github.com/duckduckgo/BareBonesBrowser.git", + "state": { + "branch": null, + "revision": "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", + "version": "0.1.0" + } + }, + { + "package": "BloomFilter", + "repositoryURL": "https://github.com/duckduckgo/bloom_cpp.git", + "state": { + "branch": null, + "revision": "8076199456290b61b4544bf2f4caf296759906a0", + "version": "3.0.0" + } + }, + { + "package": "BrowserServicesKit", + "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", + "state": { + "branch": null, + "revision": "522a9defa0eb951c9101d9f54c19d51f8ef57f4c", + "version": "169.1.0" + } + }, + { + "package": "DesignResourcesKit", + "repositoryURL": "https://github.com/duckduckgo/DesignResourcesKit", + "state": { + "branch": null, + "revision": "ae83941bb277a2750abc2d6697fa278f8c8c5f5e", + "version": "3.0.0" + } + }, + { + "package": "Autofill", + "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state": { + "branch": null, + "revision": "2b81745565db09eee8c1cd44d38eefa1011a9f0a", + "version": "12.0.1" + } + }, + { + "package": "GRDB", + "repositoryURL": "https://github.com/duckduckgo/GRDB.swift.git", + "state": { + "branch": null, + "revision": "9f049d7b97b1e68ffd86744b500660d34a9e79b8", + "version": "2.3.0" + } + }, + { + "package": "Gzip", + "repositoryURL": "https://github.com/1024jp/GzipSwift.git", + "state": { + "branch": null, + "revision": "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version": "6.0.1" + } + }, + { + "package": "FindInPageIOSJSSupport", + "repositoryURL": "https://github.com/duckduckgo/ios-js-support", + "state": { + "branch": null, + "revision": "6a6789ac8104a587316c58af75539753853b50d9", + "version": "2.0.0" + } + }, + { + "package": "Kingfisher", + "repositoryURL": "https://github.com/onevcat/Kingfisher.git", + "state": { + "branch": null, + "revision": "2ef543ee21d63734e1c004ad6c870255e8716c50", + "version": "7.12.0" + } + }, + { + "package": "Lottie", + "repositoryURL": "https://github.com/airbnb/lottie-spm.git", + "state": { + "branch": null, + "revision": "1d29eccc24cc8b75bff9f6804155112c0ffc9605", + "version": "4.4.3" + } + }, + { + "package": "OHHTTPStubs", + "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", + "state": { + "branch": null, + "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version": "9.1.0" + } + }, + { + "package": "PrivacyDashboardResources", + "repositoryURL": "https://github.com/duckduckgo/privacy-dashboard", + "state": { + "branch": null, + "revision": "348594efe2cd40ef156e915c272d02ec22f1903f", + "version": "4.2.0" + } + }, + { + "package": "Punycode", + "repositoryURL": "https://github.com/gumob/PunycodeSwift.git", + "state": { + "branch": null, + "revision": "4356ec54e073741449640d3d50a1fd24fd1e1b8b", + "version": "2.1.0" + } + }, + { + "package": "swift-argument-parser", + "repositoryURL": "https://github.com/apple/swift-argument-parser", + "state": { + "branch": null, + "revision": "46989693916f56d1186bd59ac15124caef896560", + "version": "1.3.1" + } + }, + { + "package": "swift-syntax", + "repositoryURL": "https://github.com/apple/swift-syntax.git", + "state": { + "branch": null, + "revision": "64889f0c732f210a935a0ad7cda38f77f876262d", + "version": "509.1.1" + } + }, + { + "package": "Swifter", + "repositoryURL": "https://github.com/httpswift/swifter.git", + "state": { + "branch": null, + "revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version": "1.5.0" + } + }, + { + "package": "SwiftSoup", + "repositoryURL": "https://github.com/scinfu/SwiftSoup", + "state": { + "branch": null, + "revision": "028487d4a8a291b2fe1b4392b5425b6172056148", + "version": "2.7.2" + } + }, + { + "package": "DDGSyncCrypto", + "repositoryURL": "https://github.com/duckduckgo/sync_crypto", + "state": { + "branch": null, + "revision": "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version": "0.2.0" + } + }, + { + "package": "TrackerRadarKit", + "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", + "state": { + "branch": null, + "revision": "1403e17eeeb8493b92fb9d11eb8c846bb9776581", + "version": "2.1.2" + } + }, + { + "package": "WireGuardKit", + "repositoryURL": "https://github.com/duckduckgo/wireguard-apple", + "state": { + "branch": null, + "revision": "13fd026384b1af11048451061cc1b21434990668", + "version": "1.1.3" + } + }, + { + "package": "ZIPFoundation", + "repositoryURL": "https://github.com/weichsel/ZIPFoundation.git", + "state": { + "branch": null, + "revision": "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version": "0.9.19" + } + } + ] + }, + "version": 1 } diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index d28415eec7..2b8bbdbc65 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -21,15 +21,14 @@ import Foundation import ContentScopeScripts import WebKit import Core +import Common final class YoutubePlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol - var referrer: DuckPlayerReferrer = .other { - didSet { - print(referrer) - } - } + var referrer: DuckPlayerReferrer = .other + + private var duckPlayerTemporarilyDisabled = false private struct Constants { static let SERPURL = "https://duckduckgo.com/" @@ -42,12 +41,16 @@ final class YoutubePlayerNavigationHandler { static let duckPlayerDefaultString = "default" static let settingsKey = "settings" static let httpMethod = "GET" + static let watchInYoutubePath = "/openInYoutube" + static let watchInYoutubeVideoParameter = "v" } init(duckPlayer: DuckPlayerProtocol) { self.duckPlayer = duckPlayer } + private var currentYoutubeVideoID: String? + static var htmlTemplatePath: String { guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, ofType: Constants.templateExtension, @@ -93,7 +96,6 @@ final class YoutubePlayerNavigationHandler { let duckPlayerRequest = Self.makeDuckPlayerRequest(from: request) performNavigation(duckPlayerRequest, responseHTML: html, webView: webView) } - } extension YoutubePlayerNavigationHandler: DuckNavigationHandling { @@ -105,6 +107,30 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { webView: WKWebView, completion: @escaping (WKNavigationActionPolicy) -> Void) { + + os_log("DP: Handling DuckPlayer Player Navigation for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") + + // Handle Open in Youtube Links + if let url = navigationAction.request.url, + url.scheme == "duck" { + let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) + + if urlComponents?.path == Constants.watchInYoutubePath, + let queryItems = urlComponents?.queryItems { + + if let videoParameterItem = queryItems.first(where: { $0.name == Constants.watchInYoutubeVideoParameter }), + let id = videoParameterItem.value { + os_log("DP: Triggering Watch in Youtube for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") + // Disable DP temporarily + duckPlayerTemporarilyDisabled = true + webView.load(URLRequest(url: URL.youtube(id, timestamp: nil))) + currentYoutubeVideoID = id + completion(.allow) + return + } + } + } + // Daily Unique View Pixel if let url = navigationAction.request.url, url.isDuckPlayer, @@ -114,8 +140,7 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { } // Pixel for Views From Youtube - if let url = navigationAction.request.url, - referrer == .youtube, + if referrer == .youtube, duckPlayer.settings.mode == .enabled { Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromYoutubeAutomatic, debounce: 2) } @@ -127,6 +152,7 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { let html = Self.makeHTMLFromTemplate() let newRequest = Self.makeDuckPlayerRequest(from: URLRequest(url: url)) if #available(iOS 15.0, *) { + os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") webView.loadSimulatedRequest(newRequest, responseHTML: html) completion(.allow) return @@ -137,13 +163,13 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { if let url = navigationAction.request.url, let (videoID, timestamp) = url.youtubeVideoParams, duckPlayer.settings.mode == .disabled { + os_log("DP: is Disabled. We should load original video for %s", log: .duckPlayerLog, type: .debug) webView.load(URLRequest(url: URL.youtube(videoID, timestamp: timestamp))) completion(.allow) return } completion(.allow) - } // Handle URL changes not triggered via Omnibar @@ -151,15 +177,24 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { @MainActor func handleURLChange(url: URL?, webView: WKWebView) { + // Re-enable DuckPlayer for new video + if let url = url, url.isYoutubeVideo, + !url.isDuckPlayer, + let (videoID, _) = url.youtubeVideoParams, + videoID != currentYoutubeVideoID { + duckPlayerTemporarilyDisabled = false + } + if let url = url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { webView.stopLoading() + os_log("DP: URL has changed, loading DuckPlayer for %s", log: .duckPlayerLog, type: .debug, url.absoluteString) let newURL = URL.duckPlayer(videoID, timestamp: timestamp) + currentYoutubeVideoID = videoID webView.load(URLRequest(url: newURL)) } - } // DecidePolicyFor handler to redirect relevant requests @@ -183,11 +218,13 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromOther, debounce: 2) } - if let url = navigationAction.request.url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, - duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { + duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk, + !duckPlayerTemporarilyDisabled { + os_log("DP: Handling decidePolicy for Duck Player with %s", log: .duckPlayerLog, type: .debug, url.absoluteString) + currentYoutubeVideoID = videoID webView.load(URLRequest(url: .duckPlayer(videoID, timestamp: timestamp))) completion(.allow) return @@ -208,14 +245,14 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { webView.goBack(skippingHistoryItems: 2) } - // Handle Reload for DuckPlayer Videos @MainActor func handleReload(webView: WKWebView) { if let url = webView.url, url.isDuckPlayer, !url.isDuckURLScheme, let (videoID, timestamp) = url.youtubeVideoParams, - duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { + duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { + os_log("DP: Handling DuckPlayer Reload for %s", log: .duckPlayerLog, type: .debug, url.absoluteString) webView.load(URLRequest(url: .duckPlayer(videoID, timestamp: timestamp))) } else { webView.reload() From 24441f55804c3c68ff9f203ee2fe4a2110728e0f Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Tue, 16 Jul 2024 17:46:27 +0200 Subject: [PATCH 17/37] Handle DuckPlayer internal links --- Core/UserDefaultsPropertyWrapper.swift | 4 +- .../YouTubePlayerNavigationHandler.swift | 64 +++++++++++++++---- 2 files changed, 53 insertions(+), 15 deletions(-) diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 3248521dd1..9934aefacb 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -149,9 +149,11 @@ public struct UserDefaultsWrapper { case duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode" case duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden" - + case duckPlayerLastRenderedVideo = "com.duckduckgo.ios.duckPlayer.lastRenderedVideo" + case vpnRedditWorkaroundInstalled = "com.duckduckgo.ios.vpn.workaroundInstalled" + // Debug keys case debugNewTabPageSectionsEnabledKey = "com.duckduckgo.ios.debug.newTabPageSectionsEnabled" diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index 2b8bbdbc65..42dde3d6ac 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -28,7 +28,7 @@ final class YoutubePlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol var referrer: DuckPlayerReferrer = .other - private var duckPlayerTemporarilyDisabled = false + private var isDuckPlayerTemporarilyDisabled = false private struct Constants { static let SERPURL = "https://duckduckgo.com/" @@ -47,9 +47,11 @@ final class YoutubePlayerNavigationHandler { init(duckPlayer: DuckPlayerProtocol) { self.duckPlayer = duckPlayer + print("DP Initializing") } - private var currentYoutubeVideoID: String? + @UserDefaultsWrapper(key: .duckPlayerLastRenderedVideo, defaultValue: "") + private var currentYoutubeVideoID: String static var htmlTemplatePath: String { guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, @@ -96,6 +98,18 @@ final class YoutubePlayerNavigationHandler { let duckPlayerRequest = Self.makeDuckPlayerRequest(from: request) performNavigation(duckPlayerRequest, responseHTML: html, webView: webView) } + + // Re-enables DP if required + private func updateTemporaryState(_ url: URL?) { + guard let url = url, url.isYoutubeVideo else { + return + } + + if let (videoID, _) = url.youtubeVideoParams, videoID != currentYoutubeVideoID { + isDuckPlayerTemporarilyDisabled = false + } + } + } extension YoutubePlayerNavigationHandler: DuckNavigationHandling { @@ -108,9 +122,23 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { completion: @escaping (WKNavigationActionPolicy) -> Void) { - os_log("DP: Handling DuckPlayer Player Navigation for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") + // If trying to load the same video while DP is visible + // Just open it in Youtube + if let url = navigationAction.request.url, + let (videoID, _) = url.youtubeVideoParams, + videoID == currentYoutubeVideoID { + isDuckPlayerTemporarilyDisabled = true + os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) + webView.load(URLRequest(url: URL.youtube(videoID))) + completion(.allow) + return + } + + os_log("DP: Handling DuckPlayer Player Navigation for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") + // Handle Open in Youtube Links + // duck://player/watchInYoutube?v=12345 if let url = navigationAction.request.url, url.scheme == "duck" { let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) @@ -122,9 +150,8 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { let id = videoParameterItem.value { os_log("DP: Triggering Watch in Youtube for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") // Disable DP temporarily - duckPlayerTemporarilyDisabled = true + isDuckPlayerTemporarilyDisabled = true webView.load(URLRequest(url: URL.youtube(id, timestamp: nil))) - currentYoutubeVideoID = id completion(.allow) return } @@ -153,7 +180,11 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { let newRequest = Self.makeDuckPlayerRequest(from: URLRequest(url: url)) if #available(iOS 15.0, *) { os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") - webView.loadSimulatedRequest(newRequest, responseHTML: html) + // Update State + if let (videoID, _) = newRequest.url?.youtubeVideoParams { + currentYoutubeVideoID = videoID + } + performRequest(request: newRequest, webView: webView) completion(.allow) return } @@ -177,14 +208,19 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { @MainActor func handleURLChange(url: URL?, webView: WKWebView) { - // Re-enable DuckPlayer for new video - if let url = url, url.isYoutubeVideo, - !url.isDuckPlayer, + // If trying to load the same video while DP is visible + // Just open it in Youtube + if let url = url, let (videoID, _) = url.youtubeVideoParams, - videoID != currentYoutubeVideoID { - duckPlayerTemporarilyDisabled = false + videoID == currentYoutubeVideoID { + isDuckPlayerTemporarilyDisabled = true + os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) + webView.load(URLRequest(url: URL.youtube(videoID))) + return } + updateTemporaryState(url) + if let url = url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, @@ -192,7 +228,6 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { webView.stopLoading() os_log("DP: URL has changed, loading DuckPlayer for %s", log: .duckPlayerLog, type: .debug, url.absoluteString) let newURL = URL.duckPlayer(videoID, timestamp: timestamp) - currentYoutubeVideoID = videoID webView.load(URLRequest(url: newURL)) } } @@ -203,6 +238,8 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, completion: @escaping (WKNavigationActionPolicy) -> Void, webView: WKWebView) { + + updateTemporaryState(navigationAction.request.url) // Pixel for Views From SERP if let url = navigationAction.request.url, @@ -222,9 +259,8 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk, - !duckPlayerTemporarilyDisabled { + !isDuckPlayerTemporarilyDisabled { os_log("DP: Handling decidePolicy for Duck Player with %s", log: .duckPlayerLog, type: .debug, url.absoluteString) - currentYoutubeVideoID = videoID webView.load(URLRequest(url: .duckPlayer(videoID, timestamp: timestamp))) completion(.allow) return From 169d60d58dc62ccc8e034ff24fbad9fa432eb90a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 17 Jul 2024 12:11:16 +0200 Subject: [PATCH 18/37] Reset DuckPlayer on launch --- DuckDuckGo/AppDelegate.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 3b80abc268..427363d159 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -96,6 +96,9 @@ import WebKit @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? + + @UserDefaultsWrapper(key: .duckPlayerLastRenderedVideo, defaultValue: nil) + private var duckPlayerLastRenderedVideo: String? var accountManager: AccountManager { AppDependencyProvider.shared.accountManager @@ -355,7 +358,8 @@ import WebKit AppDependencyProvider.shared.subscriptionManager.loadInitialData() setUpAutofillPixelReporter() - + initializeDuckPlayer() + return true } @@ -923,6 +927,10 @@ import WebKit } #endif } + + private func initializeDuckPlayer() { + duckPlayerLastRenderedVideo = nil + } } From c20c43058d51bdc57c01264ca67c0de9299d982a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 17 Jul 2024 14:47:53 +0200 Subject: [PATCH 19/37] Fix Watch in DP button --- DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index 42dde3d6ac..c3a73f5ae8 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -126,7 +126,8 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { // Just open it in Youtube if let url = navigationAction.request.url, let (videoID, _) = url.youtubeVideoParams, - videoID == currentYoutubeVideoID { + videoID == currentYoutubeVideoID, + duckPlayer.settings.mode == .enabled { isDuckPlayerTemporarilyDisabled = true os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) webView.load(URLRequest(url: URL.youtube(videoID))) @@ -212,7 +213,8 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { // Just open it in Youtube if let url = url, let (videoID, _) = url.youtubeVideoParams, - videoID == currentYoutubeVideoID { + videoID == currentYoutubeVideoID, + duckPlayer.settings.mode == .enabled { isDuckPlayerTemporarilyDisabled = true os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) webView.load(URLRequest(url: URL.youtube(videoID))) From a3748bf96d23474324fae5971f9f206a13544212 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 17 Jul 2024 15:20:25 +0200 Subject: [PATCH 20/37] Remove uneeded code --- .../DuckPlayer/YouTubePlayerNavigationHandler.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index c3a73f5ae8..472bc4e02e 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -177,7 +177,6 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { if let url = navigationAction.request.url, url.isDuckURLScheme, duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { - let html = Self.makeHTMLFromTemplate() let newRequest = Self.makeDuckPlayerRequest(from: URLRequest(url: url)) if #available(iOS 15.0, *) { os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") @@ -273,6 +272,9 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { // Handle Webview BackButton on DuckPlayer videos @MainActor func handleGoBack(webView: WKWebView) { + // Reset current video + currentYoutubeVideoID = "" + guard let backURL = webView.backForwardList.backItem?.url, backURL.isYoutubeVideo, backURL.youtubeVideoParams?.videoID == webView.url?.youtubeVideoParams?.videoID, @@ -286,6 +288,10 @@ extension YoutubePlayerNavigationHandler: DuckNavigationHandling { // Handle Reload for DuckPlayer Videos @MainActor func handleReload(webView: WKWebView) { + + // Reset current video + currentYoutubeVideoID = "" + if let url = webView.url, url.isDuckPlayer, !url.isDuckURLScheme, let (videoID, timestamp) = url.youtubeVideoParams, From fa616f44f2c9216e850e489842bbc30f96463739 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 17 Jul 2024 18:33:26 +0200 Subject: [PATCH 21/37] Open Settings and OpenInfo Mesasges --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 20 +++++++++++++++++++ .../DuckPlayer/YoutubePlayerUserScript.swift | 6 ++++++ DuckDuckGo/MainViewController.swift | 18 +++++++++++++++++ DuckDuckGo/SettingsRootView.swift | 2 ++ DuckDuckGo/SettingsViewModel.swift | 7 +++++++ 5 files changed, 53 insertions(+) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index df7e5ecf29..1b658bd9d8 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -87,6 +87,8 @@ protocol DuckPlayerProtocol { func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? func getUserValues(params: Any, message: WKScriptMessage) -> Encodable? func openVideoInDuckPlayer(url: URL, webView: WKWebView) + func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? + func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? @@ -135,6 +137,20 @@ final class DuckPlayer: DuckPlayerProtocol { let webView = message.webView return await self.encodedPlayerSettings(with: webView) } + + public func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? { + NotificationCenter.default.post( + name: .openDuckPlayerSettings, + object: SettingsViewModel.SettingsDeepLinkSection.duckPlayer, + userInfo: nil + ) + return nil + } + + public func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? { + // NOOP, Just prevent the assertion + return nil + } private func encodeUserValues() -> UserValues { UserValues( @@ -162,3 +178,7 @@ final class DuckPlayer: DuckPlayerProtocol { } } + +extension NSNotification.Name { + static let openDuckPlayerSettings: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.openDuckPlayerSettings") +} diff --git a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift index af283c3615..e85f0ee19b 100644 --- a/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift +++ b/DuckDuckGo/DuckPlayer/YoutubePlayerUserScript.swift @@ -35,6 +35,8 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { static let setUserValues = "setUserValues" static let getUserValues = "getUserValues" static let initialSetup = "initialSetup" + static let openSettings = "openSettings" + static let openInfo = "openInfo" } init(duckPlayer: DuckPlayerProtocol) { @@ -73,6 +75,10 @@ final class YoutubePlayerUserScript: NSObject, Subfeature { return duckPlayer.setUserValues case Handlers.initialSetup: return duckPlayer.initialSetupPlayer + case Handlers.openSettings: + return duckPlayer.openDuckPlayerSettings + case Handlers.openInfo: + return duckPlayer.openDuckPlayerInfo default: assertionFailure("YoutubePlayerUserScript: Failed to parse User Script message: \(methodName)") return nil diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 932278163c..897943a66d 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -113,6 +113,7 @@ class MainViewController: UIViewController { private var favoritesDisplayModeCancellable: AnyCancellable? private var emailCancellables = Set() private var urlInterceptorCancellables = Set() + private var settingsDeepLinkcancellables = Set() #if NETWORK_PROTECTION private let tunnelDefaults = UserDefaults.networkProtectionGroupDefaults @@ -1346,6 +1347,23 @@ class MainViewController: UIViewController { } .store(in: &urlInterceptorCancellables) } + + private func subscribeToSettingsDeeplinkNotifications() { + NotificationCenter.default.publisher(for: .settingsDeepLinkNotification) + .receive(on: DispatchQueue.main) + .sink { [weak self] notification in + switch notification.object as? SettingsViewModel.SettingsDeepLinkSection { + + case .duckPlayer: + let deepLinkTarget: SettingsViewModel.SettingsDeepLinkSection + deepLinkTarget = .duckPlayer + self?.launchSettings(deepLinkTarget: deepLinkTarget) + default: + return + } + } + .store(in: &settingsDeepLinkcancellables) + } #if NETWORK_PROTECTION private func subscribeToNetworkProtectionEvents() { diff --git a/DuckDuckGo/SettingsRootView.swift b/DuckDuckGo/SettingsRootView.swift index 32d4599313..984e42ea4f 100644 --- a/DuckDuckGo/SettingsRootView.swift +++ b/DuckDuckGo/SettingsRootView.swift @@ -117,6 +117,8 @@ struct SettingsRootView: View { SubscriptionContainerViewFactory.makeSubscribeFlow(origin: origin, navigationCoordinator: subscriptionNavigationCoordinator, subscriptionManager: AppDependencyProvider.shared.subscriptionManager) + case .duckPlayer: + SettingsDuckPlayerView() default: EmptyView() } diff --git a/DuckDuckGo/SettingsViewModel.swift b/DuckDuckGo/SettingsViewModel.swift index bf1ac1c903..1c51e13a8f 100644 --- a/DuckDuckGo/SettingsViewModel.swift +++ b/DuckDuckGo/SettingsViewModel.swift @@ -634,6 +634,7 @@ extension SettingsViewModel { case dbp case itr case subscriptionFlow(origin: String? = nil) + case duckPlayer // Add other cases as needed var id: String { @@ -642,6 +643,7 @@ extension SettingsViewModel { case .dbp: return "dbp" case .itr: return "itr" case .subscriptionFlow: return "subscriptionFlow" + case .duckPlayer: return "duckPlayer" // Ensure all cases are covered } } @@ -791,3 +793,8 @@ extension SettingsViewModel { } } + +// Deeplink notification handling +extension NSNotification.Name { + static let settingsDeepLinkNotification: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.settingsDeepLink") +} From 489fd5ea98aebfd9221ff758ecc93a534b633564 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 17 Jul 2024 18:41:13 +0200 Subject: [PATCH 22/37] Open DuckPlayer Settings --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 6 +----- DuckDuckGo/MainViewController.swift | 1 + DuckDuckGo/SettingsRootView.swift | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index 1b658bd9d8..b2fc217f6c 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -140,7 +140,7 @@ final class DuckPlayer: DuckPlayerProtocol { public func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> Encodable? { NotificationCenter.default.post( - name: .openDuckPlayerSettings, + name: .settingsDeepLinkNotification, object: SettingsViewModel.SettingsDeepLinkSection.duckPlayer, userInfo: nil ) @@ -178,7 +178,3 @@ final class DuckPlayer: DuckPlayerProtocol { } } - -extension NSNotification.Name { - static let openDuckPlayerSettings: NSNotification.Name = Notification.Name(rawValue: "com.duckduckgo.notification.openDuckPlayerSettings") -} diff --git a/DuckDuckGo/MainViewController.swift b/DuckDuckGo/MainViewController.swift index 897943a66d..77be56e94a 100644 --- a/DuckDuckGo/MainViewController.swift +++ b/DuckDuckGo/MainViewController.swift @@ -264,6 +264,7 @@ class MainViewController: UIViewController { addLaunchTabNotificationObserver() subscribeToEmailProtectionStatusNotifications() subscribeToURLInterceptorNotifications() + subscribeToSettingsDeeplinkNotifications() #if NETWORK_PROTECTION subscribeToNetworkProtectionEvents() diff --git a/DuckDuckGo/SettingsRootView.swift b/DuckDuckGo/SettingsRootView.swift index 984e42ea4f..415ce3d3bd 100644 --- a/DuckDuckGo/SettingsRootView.swift +++ b/DuckDuckGo/SettingsRootView.swift @@ -118,7 +118,7 @@ struct SettingsRootView: View { navigationCoordinator: subscriptionNavigationCoordinator, subscriptionManager: AppDependencyProvider.shared.subscriptionManager) case .duckPlayer: - SettingsDuckPlayerView() + SettingsDuckPlayerView().environmentObject(viewModel) default: EmptyView() } From 69c52767c45856454ed4d302cff6c7854d3d8571 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Wed, 17 Jul 2024 19:16:01 +0200 Subject: [PATCH 23/37] Pass tabID to navigation handler --- DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift | 4 +++- DuckDuckGo/TabViewController.swift | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift index 472bc4e02e..696910cd5e 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift @@ -26,6 +26,7 @@ import Common final class YoutubePlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol + var tabID: String var referrer: DuckPlayerReferrer = .other private var isDuckPlayerTemporarilyDisabled = false @@ -45,8 +46,9 @@ final class YoutubePlayerNavigationHandler { static let watchInYoutubeVideoParameter = "v" } - init(duckPlayer: DuckPlayerProtocol) { + init(duckPlayer: DuckPlayerProtocol, tabID: String) { self.duckPlayer = duckPlayer + self.tabID = tabID print("DP Initializing") } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 5e86fcf988..7c28a021d6 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -346,7 +346,7 @@ class TabViewController: UIViewController { registerForDownloadsNotifications() // Setup DuckPlayer navigation handler - self.youtubeNavigationHandler = YoutubePlayerNavigationHandler(duckPlayer: duckPlayer) + self.youtubeNavigationHandler = YoutubePlayerNavigationHandler(duckPlayer: duckPlayer, tabID: tabModel.uid) if #available(iOS 16.4, *) { registerForInspectableWebViewNotifications() From 1fccdac8a0f9d43fc3548b036ef15148ee166f13 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 18 Jul 2024 01:36:58 +0200 Subject: [PATCH 24/37] Move DuckPlayer one level up to TabManager --- Core/UserDefaultsPropertyWrapper.swift | 2 +- DuckDuckGo.xcodeproj/project.pbxproj | 8 +-- DuckDuckGo/AppDelegate.swift | 2 +- .../DuckPlayer/DuckNavigationHandling.swift | 1 + ...wift => DuckPlayerNavigationHandler.swift} | 12 ++--- DuckDuckGo/TabManager.swift | 10 +++- DuckDuckGo/TabViewController.swift | 53 ++++++++----------- ...ViewControllerLongPressMenuExtension.swift | 3 +- 8 files changed, 44 insertions(+), 47 deletions(-) rename DuckDuckGo/DuckPlayer/{YouTubePlayerNavigationHandler.swift => DuckPlayerNavigationHandler.swift} (97%) diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index b0b2742185..1860ed67c1 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -150,7 +150,7 @@ public struct UserDefaultsWrapper { case duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode" case duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden" - case duckPlayerLastRenderedVideo = "com.duckduckgo.ios.duckPlayer.lastRenderedVideo" + case duckPlayerActiveVideos = "com.duckduckgo.ios.duckPlayer.duckPlayerActiveVideos" case vpnRedditWorkaroundInstalled = "com.duckduckgo.ios.vpn.workaroundInstalled" diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 14780a63d4..11640ce1d7 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -818,7 +818,7 @@ D64648AF2B5993890033090B /* SubscriptionEmailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D64648AE2B5993890033090B /* SubscriptionEmailViewModel.swift */; }; D652498E2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D652498D2B515A6A0056B0DE /* SubscriptionSettingsViewModel.swift */; }; D65625902C22D307006EF297 /* DuckPlayerURLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */; }; - D65625922C22D340006EF297 /* YouTubePlayerNavigationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63FF8892C1B21C2006DE24D /* YouTubePlayerNavigationHandler.swift */; }; + D65625922C22D340006EF297 /* DuckPlayerNavigationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */; }; D65625952C22D382006EF297 /* TabViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F159BDA31F0BDB5A00B4A01D /* TabViewController.swift */; }; D65625A12C232F5E006EF297 /* SettingsDuckPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D65625A02C232F5E006EF297 /* SettingsDuckPlayerView.swift */; }; D65CEA702B6AC6C9008A759B /* Subscription.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D65CEA6F2B6AC6C9008A759B /* Subscription.xcassets */; }; @@ -2489,7 +2489,7 @@ D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = ""; }; D636DDF02C468BA7003696BD /* content-scope-scripts */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "content-scope-scripts"; path = "../content-scope-scripts"; sourceTree = ""; }; - D63FF8892C1B21C2006DE24D /* YouTubePlayerNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubePlayerNavigationHandler.swift; sourceTree = ""; }; + D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerNavigationHandler.swift; sourceTree = ""; }; D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerURLExtension.swift; sourceTree = ""; }; D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubePlayerUserScript.swift; sourceTree = ""; }; D63FF8942C1B67E8006DE24D /* YoutubeOverlayUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubeOverlayUserScript.swift; sourceTree = ""; }; @@ -4679,7 +4679,7 @@ D63FF8972C1B6A45006DE24D /* DuckPlayer.swift */, D6037E682C32F2E7009AAEC0 /* DuckPlayerSettings.swift */, D62EC3C12C248AF800FC9D04 /* DuckNavigationHandling.swift */, - D63FF8892C1B21C2006DE24D /* YouTubePlayerNavigationHandler.swift */, + D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */, D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */, D63FF8942C1B67E8006DE24D /* YoutubeOverlayUserScript.swift */, D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */, @@ -6919,7 +6919,7 @@ 1EA51376286596A000493C6A /* PrivacyIconLogic.swift in Sources */, 980891A92238504B00313A70 /* UILabelExtension.swift in Sources */, 984D035A24ACCC7D0066CFB8 /* TabViewCell.swift in Sources */, - D65625922C22D340006EF297 /* YouTubePlayerNavigationHandler.swift in Sources */, + D65625922C22D340006EF297 /* DuckPlayerNavigationHandler.swift in Sources */, 31951E8E2823003200CAF535 /* AutofillLoginDetailsHeaderView.swift in Sources */, F194FAED1F14E2B3009B4DF8 /* UIFontExtension.swift in Sources */, 98F0FC2021FF18E700CE77AB /* AutoClearSettingsViewController.swift in Sources */, diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index db3b5299dd..e851fe841d 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -97,7 +97,7 @@ import WebKit @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? - @UserDefaultsWrapper(key: .duckPlayerLastRenderedVideo, defaultValue: nil) + @UserDefaultsWrapper(key: .duckPlayerActiveVideos, defaultValue: nil) private var duckPlayerLastRenderedVideo: String? var accountManager: AccountManager { diff --git a/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift b/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift index c2e2178472..f85c4faa13 100644 --- a/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift +++ b/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift @@ -21,6 +21,7 @@ import WebKit protocol DuckNavigationHandling { var referrer: DuckPlayerReferrer { get set } + var duckPlayer: DuckPlayerProtocol { get } func handleNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView, completion: @escaping (WKNavigationActionPolicy) -> Void) diff --git a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift similarity index 97% rename from DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift rename to DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 696910cd5e..0615b1b1a4 100644 --- a/DuckDuckGo/DuckPlayer/YouTubePlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -1,5 +1,5 @@ // -// YouTubePlayerNavigationHandler.swift +// DuckPlayerNavigationHandler.swift // DuckDuckGo // // Copyright © 2024 DuckDuckGo. All rights reserved. @@ -23,10 +23,9 @@ import WebKit import Core import Common -final class YoutubePlayerNavigationHandler { +final class DuckPlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol - var tabID: String var referrer: DuckPlayerReferrer = .other private var isDuckPlayerTemporarilyDisabled = false @@ -46,13 +45,12 @@ final class YoutubePlayerNavigationHandler { static let watchInYoutubeVideoParameter = "v" } - init(duckPlayer: DuckPlayerProtocol, tabID: String) { + init(duckPlayer: DuckPlayerProtocol) { self.duckPlayer = duckPlayer - self.tabID = tabID print("DP Initializing") } - @UserDefaultsWrapper(key: .duckPlayerLastRenderedVideo, defaultValue: "") + @UserDefaultsWrapper(key: .duckPlayerActiveVideos, defaultValue: "") private var currentYoutubeVideoID: String static var htmlTemplatePath: String { @@ -114,7 +112,7 @@ final class YoutubePlayerNavigationHandler { } -extension YoutubePlayerNavigationHandler: DuckNavigationHandling { +extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Handle rendering the simulated request if the URL is duck:// // and DuckPlayer is either enabled or alwaysAsk diff --git a/DuckDuckGo/TabManager.swift b/DuckDuckGo/TabManager.swift index d0a73362c6..12ed099455 100644 --- a/DuckDuckGo/TabManager.swift +++ b/DuckDuckGo/TabManager.swift @@ -35,6 +35,7 @@ class TabManager { private let historyManager: HistoryManaging private let syncService: DDGSyncing private var previewsSource: TabPreviewsSource + private var duckPlayerNavigationHandler: DuckNavigationHandling weak var delegate: TabDelegate? @@ -52,6 +53,9 @@ class TabManager { self.bookmarksDatabase = bookmarksDatabase self.historyManager = historyManager self.syncService = syncService + + // Init Duck Player Handler + self.duckPlayerNavigationHandler = DuckPlayerNavigationHandler(duckPlayer: DuckPlayer()) registerForNotifications() } @@ -68,7 +72,8 @@ class TabManager { let controller = TabViewController.loadFromStoryboard(model: tab, bookmarksDatabase: bookmarksDatabase, historyManager: historyManager, - syncService: syncService) + syncService: syncService, + duckPlayerNavigationHandler: duckPlayerNavigationHandler) controller.applyInheritedAttribution(inheritedAttribution) controller.attachWebView(configuration: configuration, andLoadRequest: url == nil ? nil : URLRequest.userInitiated(url!), @@ -140,7 +145,8 @@ class TabManager { let controller = TabViewController.loadFromStoryboard(model: tab, bookmarksDatabase: bookmarksDatabase, historyManager: historyManager, - syncService: syncService) + syncService: syncService, + duckPlayerNavigationHandler: duckPlayerNavigationHandler) controller.attachWebView(configuration: configCopy, andLoadRequest: request, consumeCookies: !model.hasActiveTabs, diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 7c28a021d6..5d77a28c86 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -293,7 +293,8 @@ class TabViewController: UIViewController { appSettings: AppSettings = AppDependencyProvider.shared.appSettings, bookmarksDatabase: CoreDataDatabase, historyManager: HistoryManaging, - syncService: DDGSyncing) -> TabViewController { + syncService: DDGSyncing, + duckPlayerNavigationHandler: DuckNavigationHandling) -> TabViewController { let storyboard = UIStoryboard(name: "Tab", bundle: nil) let controller = storyboard.instantiateViewController(identifier: "TabViewController", creator: { coder in TabViewController(coder: coder, @@ -301,7 +302,8 @@ class TabViewController: UIViewController { appSettings: appSettings, bookmarksDatabase: bookmarksDatabase, historyManager: historyManager, - syncService: syncService) + syncService: syncService, + duckPlayerNavigationHandler: duckPlayerNavigationHandler) }) return controller } @@ -312,22 +314,22 @@ class TabViewController: UIViewController { let historyManager: HistoryManaging let historyCapture: HistoryCapture - - var duckPlayer: DuckPlayerProtocol = DuckPlayer() - var youtubeNavigationHandler: DuckNavigationHandling? + var duckPlayerNavigationHandler: DuckNavigationHandling required init?(coder aDecoder: NSCoder, tabModel: Tab, appSettings: AppSettings, bookmarksDatabase: CoreDataDatabase, historyManager: HistoryManaging, - syncService: DDGSyncing) { + syncService: DDGSyncing, + duckPlayerNavigationHandler: DuckNavigationHandling) { self.tabModel = tabModel self.appSettings = appSettings self.bookmarksDatabase = bookmarksDatabase self.historyManager = historyManager self.historyCapture = HistoryCapture(historyManager: historyManager) self.syncService = syncService + self.duckPlayerNavigationHandler = duckPlayerNavigationHandler super.init(coder: aDecoder) } @@ -344,10 +346,7 @@ class TabViewController: UIViewController { addTextSizeObserver() subscribeToEmailProtectionSignOutNotification() registerForDownloadsNotifications() - - // Setup DuckPlayer navigation handler - self.youtubeNavigationHandler = YoutubePlayerNavigationHandler(duckPlayer: duckPlayer, tabID: tabModel.uid) - + if #available(iOS 16.4, *) { registerForInspectableWebViewNotifications() } @@ -653,18 +652,15 @@ class TabViewController: UIViewController { } else if let currentHost = url?.host, let newHost = webView.url?.host, currentHost == newHost { url = webView.url - if let handler = youtubeNavigationHandler, - let url, + if let url, url.isYoutubeVideo, - duckPlayer.settings.mode == .enabled { - handler.handleURLChange(url: url, webView: webView) + duckPlayerNavigationHandler.duckPlayer.settings.mode == .enabled { + duckPlayerNavigationHandler.handleURLChange(url: url, webView: webView) } } - if var handler = youtubeNavigationHandler, - let url { - handler.referrer = url.isYoutube ? .youtube : .other - + if let url { + duckPlayerNavigationHandler.referrer = url.isYoutube ? .youtube : .other } } @@ -716,8 +712,8 @@ class TabViewController: UIViewController { public func reload() { updateContentMode() cachedRuntimeConfigurationForDomain = [:] - if let url = webView.url, url.isDuckPlayer, let handler = youtubeNavigationHandler { - handler.handleReload(webView: webView) + if let url = webView.url, url.isDuckPlayer { + duckPlayerNavigationHandler.handleReload(webView: webView) } else { webView.reload() } @@ -731,8 +727,8 @@ class TabViewController: UIViewController { func goBack() { dismissJSAlertIfNeeded() - if let url = url, url.isDuckPlayer, let handler = youtubeNavigationHandler { - handler.handleGoBack(webView: webView) + if let url = url, url.isDuckPlayer { + duckPlayerNavigationHandler.handleGoBack(webView: webView) chromeDelegate?.omniBar.resignFirstResponder() return } @@ -1648,10 +1644,9 @@ extension TabViewController: WKNavigationDelegate { } if navigationAction.isTargetingMainFrame(), - let handler = youtubeNavigationHandler, url.isYoutubeVideo, - duckPlayer.settings.mode == .enabled { - handler.handleDecidePolicyFor(navigationAction, completion: completion, webView: webView) + duckPlayerNavigationHandler.duckPlayer.settings.mode == .enabled { + duckPlayerNavigationHandler.handleDecidePolicyFor(navigationAction, completion: completion, webView: webView) return } @@ -1672,11 +1667,7 @@ extension TabViewController: WKNavigationDelegate { performBlobNavigation(navigationAction, completion: completion) case .duck: - if let handler = youtubeNavigationHandler { - handler.handleNavigation(navigationAction, webView: webView, completion: completion) - return - } - completion(.cancel) + duckPlayerNavigationHandler.handleNavigation(navigationAction, webView: webView, completion: completion) case .unknown: if navigationAction.navigationType == .linkActivated { @@ -2325,7 +2316,7 @@ extension TabViewController: UserContentControllerDelegate { userScripts.autoconsentUserScript.delegate = self // Setup DuckPlayer - userScripts.duckPlayer = duckPlayer + userScripts.duckPlayer = duckPlayerNavigationHandler.duckPlayer userScripts.youtubeOverlayScript?.webView = webView userScripts.youtubePlayerUserScript?.webView = webView diff --git a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift index e6a48170cd..b25fb38e35 100644 --- a/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift +++ b/DuckDuckGo/TabViewControllerLongPressMenuExtension.swift @@ -105,7 +105,8 @@ extension TabViewController { let tabController = TabViewController.loadFromStoryboard(model: tab, bookmarksDatabase: bookmarksDatabase, historyManager: historyManager, - syncService: syncService) + syncService: syncService, + duckPlayerNavigationHandler: duckPlayerNavigationHandler) tabController.isLinkPreview = true let configuration = WKWebViewConfiguration.nonPersistent() tabController.attachWebView(configuration: configuration, andLoadRequest: URLRequest.userInitiated(url), consumeCookies: false) From 62671f54468ace6c74aac93b2a7f2573c4c1207a Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Thu, 18 Jul 2024 03:13:59 +0200 Subject: [PATCH 25/37] Fix Watch in Youtube Action --- Core/UserDefaultsPropertyWrapper.swift | 1 - DuckDuckGo/AppDelegate.swift | 9 -- .../DuckPlayerNavigationHandler.swift | 110 ++++++------------ .../DuckPlayer/DuckPlayerURLExtension.swift | 10 +- DuckDuckGo/TabViewController.swift | 2 +- 5 files changed, 47 insertions(+), 85 deletions(-) diff --git a/Core/UserDefaultsPropertyWrapper.swift b/Core/UserDefaultsPropertyWrapper.swift index 1860ed67c1..94c8cdc6f6 100644 --- a/Core/UserDefaultsPropertyWrapper.swift +++ b/Core/UserDefaultsPropertyWrapper.swift @@ -150,7 +150,6 @@ public struct UserDefaultsWrapper { case duckPlayerMode = "com.duckduckgo.ios.duckPlayerMode" case duckPlayerAskModeOverlayHidden = "com.duckduckgo.ios.duckPlayerAskModeOverlayHidden" - case duckPlayerActiveVideos = "com.duckduckgo.ios.duckPlayer.duckPlayerActiveVideos" case vpnRedditWorkaroundInstalled = "com.duckduckgo.ios.vpn.workaroundInstalled" diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index e851fe841d..69dc0b2f90 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -96,9 +96,6 @@ import WebKit @UserDefaultsWrapper(key: .privacyConfigCustomURL, defaultValue: nil) private var privacyConfigCustomURL: String? - - @UserDefaultsWrapper(key: .duckPlayerActiveVideos, defaultValue: nil) - private var duckPlayerLastRenderedVideo: String? var accountManager: AccountManager { AppDependencyProvider.shared.accountManager @@ -366,8 +363,6 @@ import WebKit AppDependencyProvider.shared.subscriptionManager.loadInitialData() setUpAutofillPixelReporter() - initializeDuckPlayer() - if didCrashDuringCrashHandlersSetUp { Pixel.fire(pixel: .crashOnCrashHandlersSetUp) @@ -930,10 +925,6 @@ import WebKit } #endif } - - private func initializeDuckPlayer() { - duckPlayerLastRenderedVideo = nil - } } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 0615b1b1a4..ff69172d24 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -28,7 +28,11 @@ final class DuckPlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol var referrer: DuckPlayerReferrer = .other - private var isDuckPlayerTemporarilyDisabled = false + private var isDuckPlayerTemporarilyDisabled = false { + didSet { + os_log("DP: DuckPlayer Disabled: \(isDuckPlayerTemporarilyDisabled):", log: .duckPlayerLog, type: .debug) + } + } private struct Constants { static let SERPURL = "https://duckduckgo.com/" @@ -41,18 +45,15 @@ final class DuckPlayerNavigationHandler { static let duckPlayerDefaultString = "default" static let settingsKey = "settings" static let httpMethod = "GET" - static let watchInYoutubePath = "/openInYoutube" + static let watchInYoutubePath = "openInYoutube" static let watchInYoutubeVideoParameter = "v" } init(duckPlayer: DuckPlayerProtocol) { self.duckPlayer = duckPlayer - print("DP Initializing") + os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) } - @UserDefaultsWrapper(key: .duckPlayerActiveVideos, defaultValue: "") - private var currentYoutubeVideoID: String - static var htmlTemplatePath: String { guard let file = ContentScopeScripts.Bundle.path(forResource: Constants.templateName, ofType: Constants.templateExtension, @@ -99,17 +100,6 @@ final class DuckPlayerNavigationHandler { performNavigation(duckPlayerRequest, responseHTML: html, webView: webView) } - // Re-enables DP if required - private func updateTemporaryState(_ url: URL?) { - guard let url = url, url.isYoutubeVideo else { - return - } - - if let (videoID, _) = url.youtubeVideoParams, videoID != currentYoutubeVideoID { - isDuckPlayerTemporarilyDisabled = false - } - } - } extension DuckPlayerNavigationHandler: DuckNavigationHandling { @@ -122,43 +112,29 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { completion: @escaping (WKNavigationActionPolicy) -> Void) { - // If trying to load the same video while DP is visible - // Just open it in Youtube - if let url = navigationAction.request.url, - let (videoID, _) = url.youtubeVideoParams, - videoID == currentYoutubeVideoID, - duckPlayer.settings.mode == .enabled { - isDuckPlayerTemporarilyDisabled = true - os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) - webView.load(URLRequest(url: URL.youtube(videoID))) - completion(.allow) - return - } - - os_log("DP: Handling DuckPlayer Player Navigation for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") // Handle Open in Youtube Links - // duck://player/watchInYoutube?v=12345 + // duck://player/openInYoutube?v=12345 if let url = navigationAction.request.url, url.scheme == "duck" { let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) - if urlComponents?.path == Constants.watchInYoutubePath, + if urlComponents?.path == "/\(Constants.watchInYoutubePath)", let queryItems = urlComponents?.queryItems { if let videoParameterItem = queryItems.first(where: { $0.name == Constants.watchInYoutubeVideoParameter }), let id = videoParameterItem.value { - os_log("DP: Triggering Watch in Youtube for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") // Disable DP temporarily isDuckPlayerTemporarilyDisabled = true - webView.load(URLRequest(url: URL.youtube(id, timestamp: nil))) + handleURLChange(url: URL.youtube(id, timestamp: nil), webView: webView) completion(.allow) return } } } + // Daily Unique View Pixel if let url = navigationAction.request.url, url.isDuckPlayer, @@ -176,14 +152,11 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // If DuckPlayer is Enabled or in ask mode, render the video if let url = navigationAction.request.url, url.isDuckURLScheme, - duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { + duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk, + !isDuckPlayerTemporarilyDisabled { let newRequest = Self.makeDuckPlayerRequest(from: URLRequest(url: url)) if #available(iOS 15.0, *) { os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") - // Update State - if let (videoID, _) = newRequest.url?.youtubeVideoParams { - currentYoutubeVideoID = videoID - } performRequest(request: newRequest, webView: webView) completion(.allow) return @@ -195,7 +168,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { let (videoID, timestamp) = url.youtubeVideoParams, duckPlayer.settings.mode == .disabled { os_log("DP: is Disabled. We should load original video for %s", log: .duckPlayerLog, type: .debug) - webView.load(URLRequest(url: URL.youtube(videoID, timestamp: timestamp))) + handleURLChange(url: URL.youtube(videoID, timestamp: timestamp), webView: webView) completion(.allow) return } @@ -207,29 +180,30 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // such as changes triggered via JS @MainActor func handleURLChange(url: URL?, webView: WKWebView) { - - // If trying to load the same video while DP is visible - // Just open it in Youtube - if let url = url, - let (videoID, _) = url.youtubeVideoParams, - videoID == currentYoutubeVideoID, - duckPlayer.settings.mode == .enabled { - isDuckPlayerTemporarilyDisabled = true - os_log("DP: Trying to load the same video while in DuckPlayer, use Youtube:", log: .duckPlayerLog, type: .debug) - webView.load(URLRequest(url: URL.youtube(videoID))) - return - } - - updateTemporaryState(url) if let url = url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, - duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { - webView.stopLoading() - os_log("DP: URL has changed, loading DuckPlayer for %s", log: .duckPlayerLog, type: .debug, url.absoluteString) - let newURL = URL.duckPlayer(videoID, timestamp: timestamp) - webView.load(URLRequest(url: newURL)) + duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { + + os_log("DP: Handling URL change: %s", log: .duckPlayerLog, type: .debug, url.absoluteString) + var newURL = URL.duckPlayer(videoID, timestamp: timestamp) + + // IF DP is temporarily disabled, load Youtube website + // Then reset the setting + if isDuckPlayerTemporarilyDisabled { + os_log("DP: Duckplayer is temporarily disabled. Opening Youtube", log: .duckPlayerLog, type: .debug) + newURL = URL.youtube(videoID, timestamp: timestamp) + } else { + os_log("DP: Duckplayer is NOT disabled. Opening Youtube", log: .duckPlayerLog, type: .debug) + } + + // Add a delay as the Webview may not respond + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + webView.stopLoading() + webView.load(URLRequest(url: newURL)) + self.isDuckPlayerTemporarilyDisabled = false + } } } @@ -239,8 +213,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, completion: @escaping (WKNavigationActionPolicy) -> Void, webView: WKWebView) { - - updateTemporaryState(navigationAction.request.url) // Pixel for Views From SERP if let url = navigationAction.request.url, @@ -257,12 +229,11 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { } if let url = navigationAction.request.url, - url.isYoutubeVideo, - !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, - duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk, - !isDuckPlayerTemporarilyDisabled { + url.isYoutubeVideo, + !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, + duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { os_log("DP: Handling decidePolicy for Duck Player with %s", log: .duckPlayerLog, type: .debug, url.absoluteString) - webView.load(URLRequest(url: .duckPlayer(videoID, timestamp: timestamp))) + handleURLChange(url: URL.duckPlayer(videoID, timestamp: timestamp), webView: webView) completion(.allow) return } @@ -272,8 +243,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Handle Webview BackButton on DuckPlayer videos @MainActor func handleGoBack(webView: WKWebView) { - // Reset current video - currentYoutubeVideoID = "" guard let backURL = webView.backForwardList.backItem?.url, backURL.isYoutubeVideo, @@ -289,9 +258,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { @MainActor func handleReload(webView: WKWebView) { - // Reset current video - currentYoutubeVideoID = "" - if let url = webView.url, url.isDuckPlayer, !url.isDuckURLScheme, let (videoID, timestamp) = url.youtubeVideoParams, diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift b/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift index d17a9b3780..5ae878341a 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerURLExtension.swift @@ -42,8 +42,14 @@ extension URL { } static func youtube(_ videoID: String, timestamp: String? = nil) -> URL { - let url = "https://www.youtube.com/watch?v=\(videoID)".url! - return url.addingTimestamp(timestamp) + #if os(iOS) + let baseUrl = "https://m.youtube.com/watch?v=\(videoID)" + #else + let baseUrl = "https://www.youtube.com/watch?v=\(videoID)" + #endif + + let url = URL(string: baseUrl)! + return url.addingTimestamp(timestamp) } var isDuckURLScheme: Bool { diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 5d77a28c86..417e60521f 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1645,7 +1645,7 @@ extension TabViewController: WKNavigationDelegate { if navigationAction.isTargetingMainFrame(), url.isYoutubeVideo, - duckPlayerNavigationHandler.duckPlayer.settings.mode == .enabled { + duckPlayerNavigationHandler.duckPlayer.settings.mode == .enabled { duckPlayerNavigationHandler.handleDecidePolicyFor(navigationAction, completion: completion, webView: webView) return } From 5e4889753b902a7e7a8db0080cd22f67d90d26db Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 00:17:54 +0200 Subject: [PATCH 26/37] Move completion handlers out of navigation --- .../DuckPlayer/DuckNavigationHandling.swift | 8 ++---- .../DuckPlayerNavigationHandler.swift | 28 ++++++++----------- DuckDuckGo/TabViewController.swift | 7 +++-- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift b/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift index f85c4faa13..33ad44fb25 100644 --- a/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift +++ b/DuckDuckGo/DuckPlayer/DuckNavigationHandling.swift @@ -22,13 +22,9 @@ import WebKit protocol DuckNavigationHandling { var referrer: DuckPlayerReferrer { get set } var duckPlayer: DuckPlayerProtocol { get } - func handleNavigation(_ navigationAction: WKNavigationAction, - webView: WKWebView, - completion: @escaping (WKNavigationActionPolicy) -> Void) + func handleNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView) func handleURLChange(url: URL?, webView: WKWebView) - func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, - completion: @escaping (WKNavigationActionPolicy) -> Void, - webView: WKWebView) + func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, webView: WKWebView) func handleGoBack(webView: WKWebView) func handleReload(webView: WKWebView) } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index ff69172d24..15cfc77c24 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -107,10 +107,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Handle rendering the simulated request if the URL is duck:// // and DuckPlayer is either enabled or alwaysAsk @MainActor - func handleNavigation(_ navigationAction: WKNavigationAction, - webView: WKWebView, - completion: @escaping (WKNavigationActionPolicy) -> Void) { - + func handleNavigation(_ navigationAction: WKNavigationAction, webView: WKWebView) { os_log("DP: Handling DuckPlayer Player Navigation for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") @@ -128,7 +125,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Disable DP temporarily isDuckPlayerTemporarilyDisabled = true handleURLChange(url: URL.youtube(id, timestamp: nil), webView: webView) - completion(.allow) return } } @@ -158,7 +154,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { if #available(iOS 15.0, *) { os_log("DP: Loading Simulated Request for %s", log: .duckPlayerLog, type: .debug, navigationAction.request.url?.absoluteString ?? "") performRequest(request: newRequest, webView: webView) - completion(.allow) return } } @@ -169,11 +164,8 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { duckPlayer.settings.mode == .disabled { os_log("DP: is Disabled. We should load original video for %s", log: .duckPlayerLog, type: .debug) handleURLChange(url: URL.youtube(videoID, timestamp: timestamp), webView: webView) - completion(.allow) return } - - completion(.allow) } // Handle URL changes not triggered via Omnibar @@ -195,13 +187,14 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { os_log("DP: Duckplayer is temporarily disabled. Opening Youtube", log: .duckPlayerLog, type: .debug) newURL = URL.youtube(videoID, timestamp: timestamp) } else { - os_log("DP: Duckplayer is NOT disabled. Opening Youtube", log: .duckPlayerLog, type: .debug) + os_log("DP: Duckplayer is NOT disabled. Opening DuckPlayer", log: .duckPlayerLog, type: .debug) } - // Add a delay as the Webview may not respond - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - webView.stopLoading() - webView.load(URLRequest(url: newURL)) + // Load the URL + webView.load(URLRequest(url: newURL)) + + // Add a delay before resetting to allow the webview to properly render + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.isDuckPlayerTemporarilyDisabled = false } } @@ -211,9 +204,12 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // to duck://player @MainActor func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, - completion: @escaping (WKNavigationActionPolicy) -> Void, webView: WKWebView) { + if let url = navigationAction.request.url { + os_log("DP: Handling decidePolicy for Duck Player with %s", log: .duckPlayerLog, type: .debug, url.absoluteString) + } + // Pixel for Views From SERP if let url = navigationAction.request.url, navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] == Constants.SERPURL, @@ -234,10 +230,8 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { duckPlayer.settings.mode == .enabled || duckPlayer.settings.mode == .alwaysAsk { os_log("DP: Handling decidePolicy for Duck Player with %s", log: .duckPlayerLog, type: .debug, url.absoluteString) handleURLChange(url: URL.duckPlayer(videoID, timestamp: timestamp), webView: webView) - completion(.allow) return } - completion(.allow) } // Handle Webview BackButton on DuckPlayer videos diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 417e60521f..8a98e249ec 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -1646,7 +1646,8 @@ extension TabViewController: WKNavigationDelegate { if navigationAction.isTargetingMainFrame(), url.isYoutubeVideo, duckPlayerNavigationHandler.duckPlayer.settings.mode == .enabled { - duckPlayerNavigationHandler.handleDecidePolicyFor(navigationAction, completion: completion, webView: webView) + duckPlayerNavigationHandler.handleDecidePolicyFor(navigationAction, webView: webView) + completion(.allow) return } @@ -1667,7 +1668,9 @@ extension TabViewController: WKNavigationDelegate { performBlobNavigation(navigationAction, completion: completion) case .duck: - duckPlayerNavigationHandler.handleNavigation(navigationAction, webView: webView, completion: completion) + duckPlayerNavigationHandler.handleNavigation(navigationAction, webView: webView) + completion(.cancel) + return case .unknown: if navigationAction.navigationType == .linkActivated { From b06cdffa6d5cb5fb16c5a66de19055d75b2c7d0e Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 11:31:38 +0200 Subject: [PATCH 27/37] Updated DuckHandler Tests --- .../DuckPlayerNavigationHandler.swift | 32 ++- DuckDuckGoTests/DuckPlayerMocks.swift | 8 + ...YoutublePlayerNavigationHandlerTests.swift | 233 +++++++----------- 3 files changed, 117 insertions(+), 156 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 15cfc77c24..cc7d279d39 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -27,12 +27,9 @@ final class DuckPlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol var referrer: DuckPlayerReferrer = .other + var lastHandledVideoID: String? - private var isDuckPlayerTemporarilyDisabled = false { - didSet { - os_log("DP: DuckPlayer Disabled: \(isDuckPlayerTemporarilyDisabled):", log: .duckPlayerLog, type: .debug) - } - } + var isDuckPlayerTemporarilyDisabled = false private struct Constants { static let SERPURL = "https://duckduckgo.com/" @@ -130,7 +127,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { } } - // Daily Unique View Pixel if let url = navigationAction.request.url, url.isDuckPlayer, @@ -172,7 +168,16 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // such as changes triggered via JS @MainActor func handleURLChange(url: URL?, webView: WKWebView) { - + + // Do not handle the URL if the video was just handled + if let url = url, + url.isYoutubeVideo || url.isDuckPlayer, + let (videoID, timestamp) = url.youtubeVideoParams, + lastHandledVideoID == videoID, + !isDuckPlayerTemporarilyDisabled { + return + } + if let url = url, url.isYoutubeVideo, !url.isDuckPlayer, let (videoID, timestamp) = url.youtubeVideoParams, @@ -193,8 +198,9 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Load the URL webView.load(URLRequest(url: newURL)) - // Add a delay before resetting to allow the webview to properly render + // Add a delay before resetting to allow the Webview to properly render DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + self.lastHandledVideoID = videoID self.isDuckPlayerTemporarilyDisabled = false } } @@ -206,10 +212,16 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { func handleDecidePolicyFor(_ navigationAction: WKNavigationAction, webView: WKWebView) { - if let url = navigationAction.request.url { - os_log("DP: Handling decidePolicy for Duck Player with %s", log: .duckPlayerLog, type: .debug, url.absoluteString) + // Do not handle the URL if the video was just handled + if let url = navigationAction.request.url, + url.isYoutubeVideo || url.isDuckPlayer, + let (videoID, timestamp) = url.youtubeVideoParams, + lastHandledVideoID == videoID, + !isDuckPlayerTemporarilyDisabled { + return } + // Pixel for Views From SERP if let url = navigationAction.request.url, navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] == Constants.SERPURL, diff --git a/DuckDuckGoTests/DuckPlayerMocks.swift b/DuckDuckGoTests/DuckPlayerMocks.swift index aea76a748a..ed84be6f1d 100644 --- a/DuckDuckGoTests/DuckPlayerMocks.swift +++ b/DuckDuckGoTests/DuckPlayerMocks.swift @@ -110,6 +110,14 @@ final class MockDuckPlayerSettings: DuckPlayerSettingsProtocol { } final class MockDuckPlayer: DuckPlayerProtocol { + func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + + func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + var settings: any DuckPlayerSettingsProtocol init(settings: DuckPlayerSettingsProtocol) { diff --git a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift index fe4f76eb09..77e4ad59b5 100644 --- a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift +++ b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift @@ -25,14 +25,14 @@ import BrowserServicesKit @testable import DuckDuckGo -class YoutubePlayerNavigationHandlerTests: XCTestCase { +class DuckPlayerNavigationHandlerTests: XCTestCase { var webView: WKWebView! var mockWebView: MockWebView! var mockNavigationDelegate: MockWKNavigationDelegate! var mockAppSettings: AppSettingsMock! var mockPrivacyConfig: PrivacyConfigurationManagerMock! - + override func setUp() { super.setUp() webView = WKWebView() @@ -52,7 +52,7 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { // Test for htmlTemplatePath existence func testHtmlTemplatePathExists() { - let templatePath = YoutubePlayerNavigationHandler.htmlTemplatePath + let templatePath = DuckPlayerNavigationHandler.htmlTemplatePath let fileExists = FileManager.default.fileExists(atPath: templatePath) XCTAssertFalse(templatePath.isEmpty, "The template path should not be empty") XCTAssertTrue(fileExists, "The template file should exist at the specified path") @@ -62,7 +62,7 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { func testMakeDuckPlayerRequestFromOriginalRequest() { let originalRequest = URLRequest(url: URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")!) - let duckPlayerRequest = YoutubePlayerNavigationHandler.makeDuckPlayerRequest(from: originalRequest) + let duckPlayerRequest = DuckPlayerNavigationHandler.makeDuckPlayerRequest(from: originalRequest) XCTAssertEqual(duckPlayerRequest.url?.host, "www.youtube-nocookie.com") XCTAssertEqual(duckPlayerRequest.url?.path, "/embed/abc123") @@ -76,7 +76,7 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { let videoID = "abc123" let timestamp = "10s" - let duckPlayerRequest = YoutubePlayerNavigationHandler.makeDuckPlayerRequest(for: videoID, timestamp: timestamp) + let duckPlayerRequest = DuckPlayerNavigationHandler.makeDuckPlayerRequest(for: videoID, timestamp: timestamp) XCTAssertEqual(duckPlayerRequest.url?.host, "www.youtube-nocookie.com") XCTAssertEqual(duckPlayerRequest.url?.path, "/embed/abc123") @@ -87,24 +87,23 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { // Test for makeHTMLFromTemplate func testMakeHTMLFromTemplate() { - let expectedHtml = try? String(contentsOfFile: YoutubePlayerNavigationHandler.htmlTemplatePath) - let html = YoutubePlayerNavigationHandler.makeHTMLFromTemplate() + let expectedHtml = try? String(contentsOfFile: DuckPlayerNavigationHandler.htmlTemplatePath) + let html = DuckPlayerNavigationHandler.makeHTMLFromTemplate() XCTAssertEqual(html, expectedHtml) } - // Test for handleURLChange + // MARK: handleURLChange tests @MainActor func testHandleURLChangeDuckPlayerEnabled() { let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.alwaysAsk) + playerSettings.setMode(.enabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) handler.handleURLChange(url: youtubeURL, webView: mockWebView) - XCTAssertTrue(mockWebView.didStopLoadingCalled, "Expected stopLoading to be called") XCTAssertNotNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") if let loadedRequest = mockWebView.lastLoadedRequest { @@ -113,6 +112,7 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { XCTAssertEqual(loadedRequest.url?.path, "/abc123") XCTAssertEqual(loadedRequest.url?.query?.contains("t=10s"), true) } + } @MainActor @@ -122,206 +122,147 @@ class YoutubePlayerNavigationHandlerTests: XCTestCase { let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) playerSettings.setMode(.disabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) handler.handleURLChange(url: youtubeURL, webView: mockWebView) - XCTAssertFalse(mockWebView.didStopLoadingCalled, "Expected stopLoading Not to be called") - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request NOT to be loaded") } @MainActor - func testHandleURLChangeForNonYouTubeVideo() { - let nonYouTubeURL = URL(string: "https://www.google.com")! - - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.disabled) - let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleURLChange(url: nonYouTubeURL, webView: mockWebView) - - XCTAssertFalse(mockWebView.didStopLoadingCalled, "Expected stopLoading not to be called") - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") - } - - // Test for handleDecidePolicyFor - @MainActor - func testHandleDecidePolicyForWithDuckPlayerEnabled() { + func testHandleURLChangeDuckPlayerTemporarilyDisabled() { let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let expectation = self.expectation(description: "Completion handler called") - - var navigationPolicy: WKNavigationActionPolicy? let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.alwaysAsk) + playerSettings.setMode(.enabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) + handler.isDuckPlayerTemporarilyDisabled = true - waitForExpectations(timeout: 1, handler: nil) + handler.handleURLChange(url: youtubeURL, webView: mockWebView) - XCTAssertEqual(navigationPolicy, .allow, "Expected navigation policy to be .allow") XCTAssertNotNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") if let loadedRequest = mockWebView.lastLoadedRequest { - XCTAssertEqual(loadedRequest.url?.scheme, "duck") - XCTAssertEqual(loadedRequest.url?.host, "player") - XCTAssertEqual(loadedRequest.url?.path, "/abc123") + XCTAssertEqual(loadedRequest.url?.scheme, "https") + XCTAssertEqual(loadedRequest.url?.host, "m.youtube.com") + XCTAssertEqual(loadedRequest.url?.path, "/watch") + XCTAssertEqual(loadedRequest.url?.query?.contains("v=abc123"), true) XCTAssertEqual(loadedRequest.url?.query?.contains("t=10s"), true) } - } @MainActor - func testHandleDecidePolicyForWithDuckPlayerDisabled() { - let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! - let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) - let expectation = self.expectation(description: "Completion handler called") - - var navigationPolicy: WKNavigationActionPolicy? + func testHandleURLChangeNonYouTubeURL() { + let nonYouTubeURL = URL(string: "https://www.google.com")! let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.disabled) + playerSettings.setMode(.enabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) - - waitForExpectations(timeout: 1, handler: nil) + handler.handleURLChange(url: nonYouTubeURL, webView: mockWebView) - XCTAssertEqual(navigationPolicy, .allow, "Expected navigation policy to be .allow") - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") - + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request NOT to be loaded") } @MainActor - func testHandleDecidePolicyForNonYouTubeVideoWithDuckPlayerEnabled() { - let nonYouTubeURL = URL(string: "https://www.google.com")! - let navigationAction = MockNavigationAction(request: URLRequest(url: nonYouTubeURL)) - let expectation = self.expectation(description: "Completion handler called") - - var navigationPolicy: WKNavigationActionPolicy? + func testHandleNavigationOpenInYoutubeLink() { + let duckURL = URL(string: "duck://player/openInYoutube?v=12345")! + let navigationAction = MockNavigationAction(request: URLRequest(url: duckURL)) let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.alwaysAsk) + playerSettings.setMode(.enabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + + handler.handleNavigation(navigationAction, webView: mockWebView) - waitForExpectations(timeout: 1, handler: nil) + XCTAssertTrue(handler.isDuckPlayerTemporarilyDisabled, "Expected DuckPlayer to be temporarily disabled") + XCTAssertNotNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") - XCTAssertEqual(navigationPolicy, .allow, "Expected navigation policy to be .allow") - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + if let loadedRequest = mockWebView.lastLoadedRequest { + XCTAssertEqual(loadedRequest.url?.scheme, "https") + XCTAssertEqual(loadedRequest.url?.host, "m.youtube.com") + XCTAssertEqual(loadedRequest.url?.path, "/watch") + XCTAssertEqual(loadedRequest.url?.query?.contains("v=12345"), true) + } } @MainActor - func testHandleDecidePolicyForNonYouTubeVideoWithDuckPlayerDisabled() { - let nonYouTubeURL = URL(string: "https://www.google.com")! - let navigationAction = MockNavigationAction(request: URLRequest(url: nonYouTubeURL)) - let expectation = self.expectation(description: "Completion handler called") - - var navigationPolicy: WKNavigationActionPolicy? + func testHandleNavigationDuckPlayerEnabledAlreadyInDuckPlayer() { + let duckPlayerURL = URL(string: "duck://player/CYTASDSD")! + let navigationAction = MockNavigationAction(request: URLRequest(url: duckPlayerURL)) let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.disabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleDecidePolicyFor(navigationAction, completion: { policy in - navigationPolicy = policy - expectation.fulfill() - }, webView: mockWebView) + player.settings.setMode(.enabled) - waitForExpectations(timeout: 1, handler: nil) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + handler.handleNavigation(navigationAction, webView: mockWebView) + + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") - XCTAssertEqual(navigationPolicy, .allow, "Expected navigation policy to be .allow") - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") } + @MainActor - func testHandleReloadForDuckPlayerVideoWithDuckPlayerEnabled() { - let duckPlayerURL = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - - mockWebView.setCurrentURL(duckPlayerURL) + func testHandleNavigationDuckPlayerDisabled() { + let duckPlayerURL = URL(string: "duck://player/CUIUIIUI")! + let navigationAction = MockNavigationAction(request: URLRequest(url: duckPlayerURL)) let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.alwaysAsk) + playerSettings.setMode(.disabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleReload(webView: mockWebView) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + + handler.handleNavigation(navigationAction, webView: mockWebView) - XCTAssertNotNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") - if let loadedRequest = mockWebView.lastLoadedRequest { - XCTAssertEqual(loadedRequest.url?.scheme, "duck") - XCTAssertEqual(loadedRequest.url?.host, "player") - XCTAssertEqual(loadedRequest.url?.path, "/abc123") - XCTAssertEqual(loadedRequest.url?.query?.contains("t=10s"), true) - } } @MainActor - func testHandleReloadForDuckPlayerVideoWithDuckPlayerDisabled() { - let duckPlayerURL = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! - - mockWebView.setCurrentURL(duckPlayerURL) - + func testHandleDecidePolicyForVideoJustHandled() { + let youtubeURL = URL(string: "https://www.youtube.com/watch?v=abc123&t=10s")! + let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.disabled) + playerSettings.setMode(.enabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleReload(webView: mockWebView) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + // Call handleDecidePolicyFor twice with the same URL to simulate handling the same video twice + handler.handleDecidePolicyFor(navigationAction, webView: mockWebView) - } - - @MainActor - func testHandleReloadForNonDuckPlayerVideoWithDuckPlayerEnabled() { - let nonDuckPlayerURL = URL(string: "https://www.google.com")! + // Wait for 0.8 seconds to simulate the time delay + let expectation = self.expectation(description: "Wait for 0.8 seconds") + DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { + handler.handleDecidePolicyFor(navigationAction, webView: self.mockWebView) + expectation.fulfill() + } - // Simulate the current URL - mockWebView.setCurrentURL(nonDuckPlayerURL) + waitForExpectations(timeout: 1.0, handler: nil) - let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.alwaysAsk) - let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleReload(webView: mockWebView) - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + // Verify that the second call did not load a new request + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected no new request to be loaded since video was just handled") } @MainActor - func testHandleReloadForNonDuckPlayerVideoWithDuckPlayerDisabled() { - let nonDuckPlayerURL = URL(string: "https://www.google.com")! - - // Simulate the current URL - mockWebView.setCurrentURL(nonDuckPlayerURL) + func testHandleDecidePolicyForTransformYoutubeURL() { + let youtubeURL = URL(string: "https://m.youtube.com/watch?v=abc123&t=10s")! + let navigationAction = MockNavigationAction(request: URLRequest(url: youtubeURL)) let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) - playerSettings.setMode(.disabled) + playerSettings.setMode(.enabled) let player = MockDuckPlayer(settings: playerSettings) - let handler = YoutubePlayerNavigationHandler(duckPlayer: player) - - handler.handleReload(webView: mockWebView) - XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + + handler.handleDecidePolicyFor(navigationAction, webView: mockWebView) + + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") + } - + } From 5ae8fd2b7879305716350ae7dd5b02d1789d35b7 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 11:52:24 +0200 Subject: [PATCH 28/37] MOAR tests --- ...YoutublePlayerNavigationHandlerTests.swift | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift index 77e4ad59b5..877980dc8e 100644 --- a/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift +++ b/DuckDuckGoTests/YoutublePlayerNavigationHandlerTests.swift @@ -264,5 +264,54 @@ class DuckPlayerNavigationHandlerTests: XCTestCase { XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request to be loaded") } + + @MainActor + func testHandleReloadForDuckPlayerVideoWithDuckPlayerDisabled() { + let duckPlayerURL = URL(string: "https://www.youtube-nocookie.com/embed/abc123?t=10s")! + + mockWebView.setCurrentURL(duckPlayerURL) + + let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + playerSettings.setMode(.disabled) + let player = MockDuckPlayer(settings: playerSettings) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + + handler.handleReload(webView: mockWebView) + + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + + } + + @MainActor + func testHandleReloadForNonDuckPlayerVideoWithDuckPlayerEnabled() { + let nonDuckPlayerURL = URL(string: "https://www.google.com")! + + // Simulate the current URL + mockWebView.setCurrentURL(nonDuckPlayerURL) + + let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + playerSettings.setMode(.alwaysAsk) + let player = MockDuckPlayer(settings: playerSettings) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + + handler.handleReload(webView: mockWebView) + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + } + + @MainActor + func testHandleReloadForNonDuckPlayerVideoWithDuckPlayerDisabled() { + let nonDuckPlayerURL = URL(string: "https://www.google.com")! + + // Simulate the current URL + mockWebView.setCurrentURL(nonDuckPlayerURL) + + let playerSettings = MockDuckPlayerSettings(appSettings: mockAppSettings, privacyConfigManager: mockPrivacyConfig) + playerSettings.setMode(.disabled) + let player = MockDuckPlayer(settings: playerSettings) + let handler = DuckPlayerNavigationHandler(duckPlayer: player) + + handler.handleReload(webView: mockWebView) + XCTAssertNil(mockWebView.lastLoadedRequest, "Expected a new request not to be loaded") + } } From c06830e32762ac5e9416bf8a128263b903a5fd56 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 12:16:24 +0200 Subject: [PATCH 29/37] Enable Info Button --- DuckDuckGo/DuckPlayer/DuckPlayer.swift | 18 +++++++++++++++++- DuckDuckGo/TabViewController.swift | 5 ++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayer.swift b/DuckDuckGo/DuckPlayer/DuckPlayer.swift index b2fc217f6c..4192367a0d 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayer.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayer.swift @@ -81,6 +81,7 @@ public enum DuckPlayerReferrer { protocol DuckPlayerProtocol { var settings: DuckPlayerSettingsProtocol { get } + var hostView: UIViewController? { get } init(settings: DuckPlayerSettingsProtocol) @@ -92,6 +93,8 @@ protocol DuckPlayerProtocol { func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> Encodable? func initialSetupOverlay(params: Any, message: WKScriptMessage) async -> Encodable? + + func setHostViewController(_ vc: UIViewController) } final class DuckPlayer: DuckPlayerProtocol { @@ -100,11 +103,18 @@ final class DuckPlayer: DuckPlayerProtocol { static let commonName = "Duck Player" private(set) var settings: DuckPlayerSettingsProtocol + private(set) var hostView: UIViewController? init(settings: DuckPlayerSettingsProtocol = DuckPlayerSettings()) { self.settings = settings } + // Sets a presenting VC, so DuckPlayer can present the + // info sheet directly + public func setHostViewController(_ vc: UIViewController) { + hostView = vc + } + // MARK: - Common Message Handlers public func setUserValues(params: Any, message: WKScriptMessage) -> Encodable? { @@ -147,8 +157,14 @@ final class DuckPlayer: DuckPlayerProtocol { return nil } + @MainActor + public func presentDuckPlayerInfo() { + guard let hostView else { return } + DuckPlayerModalPresenter().presentDuckPlayerFeatureModal(on: hostView) + } + public func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> Encodable? { - // NOOP, Just prevent the assertion + await presentDuckPlayerInfo() return nil } diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 8a98e249ec..6155322e44 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -346,7 +346,10 @@ class TabViewController: UIViewController { addTextSizeObserver() subscribeToEmailProtectionSignOutNotification() registerForDownloadsNotifications() - + + // Register as DuckPlayer host View + duckPlayerNavigationHandler.duckPlayer.setHostViewController(self) + if #available(iOS 16.4, *) { registerForInspectableWebViewNotifications() } From a88c7daa0058abc4c7e9676d67fa5dcf9a638929 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 16:05:37 +0200 Subject: [PATCH 30/37] Removed unused vars --- DuckDuckGo.xcodeproj/project.pbxproj | 2 - .../xcshareddata/swiftpm/Package.resolved | 413 +++++++++--------- .../DuckPlayerNavigationHandler.swift | 4 +- 3 files changed, 212 insertions(+), 207 deletions(-) diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index 11640ce1d7..65b191254b 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -2488,7 +2488,6 @@ D62EC3C12C248AF800FC9D04 /* DuckNavigationHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckNavigationHandling.swift; sourceTree = ""; }; D63657182A7BAE7C001AF19D /* EmailManagerRequestDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmailManagerRequestDelegate.swift; sourceTree = ""; }; D63677F42BBDB1C300605BA5 /* DaxLogoNavbarTitle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DaxLogoNavbarTitle.swift; sourceTree = ""; }; - D636DDF02C468BA7003696BD /* content-scope-scripts */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "content-scope-scripts"; path = "../content-scope-scripts"; sourceTree = ""; }; D63FF8892C1B21C2006DE24D /* DuckPlayerNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerNavigationHandler.swift; sourceTree = ""; }; D63FF88B2C1B21ED006DE24D /* DuckPlayerURLExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DuckPlayerURLExtension.swift; sourceTree = ""; }; D63FF8932C1B67E8006DE24D /* YoutubePlayerUserScript.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = YoutubePlayerUserScript.swift; sourceTree = ""; }; @@ -3682,7 +3681,6 @@ 84E341891E2F7EFB00BDBA6F = { isa = PBXGroup; children = ( - D636DDF02C468BA7003696BD /* content-scope-scripts */, EE3B98EB2A963515002F63A0 /* WidgetsExtensionAlpha.entitlements */, 6FB030C7234331B400A10DB9 /* Configuration.xcconfig */, EEB8FDB92A990AEE00EBEDCF /* Configuration-Alpha.xcconfig */, diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 81fec034ad..2dd6c45915 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,205 +1,212 @@ { - "object": { - "pins": [ - { - "package": "AppleToolbox", - "repositoryURL": "https://github.com/duckduckgo/apple-toolbox.git", - "state": { - "branch": null, - "revision": "0c13c5f056805f2d403618ccc3bfb833c303c68d", - "version": "3.1.2" - } - }, - { - "package": "BareBonesBrowserKit", - "repositoryURL": "https://github.com/duckduckgo/BareBonesBrowser.git", - "state": { - "branch": null, - "revision": "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", - "version": "0.1.0" - } - }, - { - "package": "BloomFilter", - "repositoryURL": "https://github.com/duckduckgo/bloom_cpp.git", - "state": { - "branch": null, - "revision": "8076199456290b61b4544bf2f4caf296759906a0", - "version": "3.0.0" - } - }, - { - "package": "BrowserServicesKit", - "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", - "state": { - "branch": null, - "revision": "09844ec2d0c9d2312e02c90527e8df063db89318", - "version": "171.2.1" - } - }, - { - "package": "DesignResourcesKit", - "repositoryURL": "https://github.com/duckduckgo/DesignResourcesKit", - "state": { - "branch": null, - "revision": "ae83941bb277a2750abc2d6697fa278f8c8c5f5e", - "version": "3.0.0" - } - }, - { - "package": "Autofill", - "repositoryURL": "https://github.com/duckduckgo/duckduckgo-autofill.git", - "state": { - "branch": null, - "revision": "2b81745565db09eee8c1cd44d38eefa1011a9f0a", - "version": "12.0.1" - } - }, - { - "package": "GRDB", - "repositoryURL": "https://github.com/duckduckgo/GRDB.swift.git", - "state": { - "branch": null, - "revision": "9f049d7b97b1e68ffd86744b500660d34a9e79b8", - "version": "2.3.0" - } - }, - { - "package": "Gzip", - "repositoryURL": "https://github.com/1024jp/GzipSwift.git", - "state": { - "branch": null, - "revision": "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", - "version": "6.0.1" - } - }, - { - "package": "FindInPageIOSJSSupport", - "repositoryURL": "https://github.com/duckduckgo/ios-js-support", - "state": { - "branch": null, - "revision": "6a6789ac8104a587316c58af75539753853b50d9", - "version": "2.0.0" - } - }, - { - "package": "Kingfisher", - "repositoryURL": "https://github.com/onevcat/Kingfisher.git", - "state": { - "branch": null, - "revision": "2ef543ee21d63734e1c004ad6c870255e8716c50", - "version": "7.12.0" - } - }, - { - "package": "Lottie", - "repositoryURL": "https://github.com/airbnb/lottie-spm.git", - "state": { - "branch": null, - "revision": "1d29eccc24cc8b75bff9f6804155112c0ffc9605", - "version": "4.4.3" - } - }, - { - "package": "OHHTTPStubs", - "repositoryURL": "https://github.com/AliSoftware/OHHTTPStubs.git", - "state": { - "branch": null, - "revision": "12f19662426d0434d6c330c6974d53e2eb10ecd9", - "version": "9.1.0" - } - }, - { - "package": "PrivacyDashboardResources", - "repositoryURL": "https://github.com/duckduckgo/privacy-dashboard", - "state": { - "branch": null, - "revision": "348594efe2cd40ef156e915c272d02ec22f1903f", - "version": "4.2.0" - } - }, - { - "package": "Punycode", - "repositoryURL": "https://github.com/gumob/PunycodeSwift.git", - "state": { - "branch": null, - "revision": "4356ec54e073741449640d3d50a1fd24fd1e1b8b", - "version": "2.1.0" - } - }, - { - "package": "swift-argument-parser", - "repositoryURL": "https://github.com/apple/swift-argument-parser", - "state": { - "branch": null, - "revision": "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", - "version": "1.4.0" - } - }, - { - "package": "swift-syntax", - "repositoryURL": "https://github.com/apple/swift-syntax.git", - "state": { - "branch": null, - "revision": "64889f0c732f210a935a0ad7cda38f77f876262d", - "version": "509.1.1" - } - }, - { - "package": "Swifter", - "repositoryURL": "https://github.com/httpswift/swifter.git", - "state": { - "branch": null, - "revision": "9483a5d459b45c3ffd059f7b55f9638e268632fd", - "version": "1.5.0" - } - }, - { - "package": "SwiftSoup", - "repositoryURL": "https://github.com/scinfu/SwiftSoup", - "state": { - "branch": null, - "revision": "028487d4a8a291b2fe1b4392b5425b6172056148", - "version": "2.7.2" - } - }, - { - "package": "DDGSyncCrypto", - "repositoryURL": "https://github.com/duckduckgo/sync_crypto", - "state": { - "branch": null, - "revision": "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", - "version": "0.2.0" - } - }, - { - "package": "TrackerRadarKit", - "repositoryURL": "https://github.com/duckduckgo/TrackerRadarKit", - "state": { - "branch": null, - "revision": "1403e17eeeb8493b92fb9d11eb8c846bb9776581", - "version": "2.1.2" - } - }, - { - "package": "WireGuardKit", - "repositoryURL": "https://github.com/duckduckgo/wireguard-apple", - "state": { - "branch": null, - "revision": "13fd026384b1af11048451061cc1b21434990668", - "version": "1.1.3" - } - }, - { - "package": "ZIPFoundation", - "repositoryURL": "https://github.com/weichsel/ZIPFoundation.git", - "state": { - "branch": null, - "revision": "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", - "version": "0.9.19" - } - } - ] - }, - "version": 1 + "pins" : [ + { + "identity" : "apple-toolbox", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/apple-toolbox.git", + "state" : { + "revision" : "0c13c5f056805f2d403618ccc3bfb833c303c68d", + "version" : "3.1.2" + } + }, + { + "identity" : "barebonesbrowser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/BareBonesBrowser.git", + "state" : { + "revision" : "31e5bfedc3c2ca005640c4bf2b6959d69b0e18b9", + "version" : "0.1.0" + } + }, + { + "identity" : "bloom_cpp", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/bloom_cpp.git", + "state" : { + "revision" : "8076199456290b61b4544bf2f4caf296759906a0", + "version" : "3.0.0" + } + }, + { + "identity" : "browserserviceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/DuckDuckGo/BrowserServicesKit", + "state" : { + "revision" : "09844ec2d0c9d2312e02c90527e8df063db89318", + "version" : "171.2.1" + } + }, + { + "identity" : "content-scope-scripts", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/content-scope-scripts", + "state" : { + "revision" : "dc26bfc6e33ad9c79a719b7f21d5ca0564db1859", + "version" : "6.3.0" + } + }, + { + "identity" : "designresourceskit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/DesignResourcesKit", + "state" : { + "revision" : "ae83941bb277a2750abc2d6697fa278f8c8c5f5e", + "version" : "3.0.0" + } + }, + { + "identity" : "duckduckgo-autofill", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/duckduckgo-autofill.git", + "state" : { + "revision" : "2b81745565db09eee8c1cd44d38eefa1011a9f0a", + "version" : "12.0.1" + } + }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/GRDB.swift.git", + "state" : { + "revision" : "9f049d7b97b1e68ffd86744b500660d34a9e79b8", + "version" : "2.3.0" + } + }, + { + "identity" : "gzipswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/1024jp/GzipSwift.git", + "state" : { + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" + } + }, + { + "identity" : "ios-js-support", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/ios-js-support", + "state" : { + "revision" : "6a6789ac8104a587316c58af75539753853b50d9", + "version" : "2.0.0" + } + }, + { + "identity" : "kingfisher", + "kind" : "remoteSourceControl", + "location" : "https://github.com/onevcat/Kingfisher.git", + "state" : { + "revision" : "2ef543ee21d63734e1c004ad6c870255e8716c50", + "version" : "7.12.0" + } + }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "revision" : "1d29eccc24cc8b75bff9f6804155112c0ffc9605", + "version" : "4.4.3" + } + }, + { + "identity" : "ohhttpstubs", + "kind" : "remoteSourceControl", + "location" : "https://github.com/AliSoftware/OHHTTPStubs.git", + "state" : { + "revision" : "12f19662426d0434d6c330c6974d53e2eb10ecd9", + "version" : "9.1.0" + } + }, + { + "identity" : "privacy-dashboard", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/privacy-dashboard", + "state" : { + "revision" : "348594efe2cd40ef156e915c272d02ec22f1903f", + "version" : "4.2.0" + } + }, + { + "identity" : "punycodeswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gumob/PunycodeSwift.git", + "state" : { + "revision" : "4356ec54e073741449640d3d50a1fd24fd1e1b8b", + "version" : "2.1.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser", + "state" : { + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "64889f0c732f210a935a0ad7cda38f77f876262d", + "version" : "509.1.1" + } + }, + { + "identity" : "swifter", + "kind" : "remoteSourceControl", + "location" : "https://github.com/httpswift/swifter.git", + "state" : { + "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd", + "version" : "1.5.0" + } + }, + { + "identity" : "swiftsoup", + "kind" : "remoteSourceControl", + "location" : "https://github.com/scinfu/SwiftSoup", + "state" : { + "revision" : "028487d4a8a291b2fe1b4392b5425b6172056148", + "version" : "2.7.2" + } + }, + { + "identity" : "sync_crypto", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/sync_crypto", + "state" : { + "revision" : "2ab6ab6f0f96b259c14c2de3fc948935fc16ac78", + "version" : "0.2.0" + } + }, + { + "identity" : "trackerradarkit", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/TrackerRadarKit", + "state" : { + "revision" : "1403e17eeeb8493b92fb9d11eb8c846bb9776581", + "version" : "2.1.2" + } + }, + { + "identity" : "wireguard-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/duckduckgo/wireguard-apple", + "state" : { + "revision" : "13fd026384b1af11048451061cc1b21434990668", + "version" : "1.1.3" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation.git", + "state" : { + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" + } + } + ], + "version" : 2 } diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index cc7d279d39..04fee40a0f 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -172,7 +172,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Do not handle the URL if the video was just handled if let url = url, url.isYoutubeVideo || url.isDuckPlayer, - let (videoID, timestamp) = url.youtubeVideoParams, + let (videoID, _) = url.youtubeVideoParams, lastHandledVideoID == videoID, !isDuckPlayerTemporarilyDisabled { return @@ -215,7 +215,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Do not handle the URL if the video was just handled if let url = navigationAction.request.url, url.isYoutubeVideo || url.isDuckPlayer, - let (videoID, timestamp) = url.youtubeVideoParams, + let (videoID, _) = url.youtubeVideoParams, lastHandledVideoID == videoID, !isDuckPlayerTemporarilyDisabled { return From bee6056504baf0dfa7bb2710a969232d52ba33cd Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 16:41:02 +0200 Subject: [PATCH 31/37] Update comment --- DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 04fee40a0f..c8b0f9e5e9 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -198,7 +198,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Load the URL webView.load(URLRequest(url: newURL)) - // Add a delay before resetting to allow the Webview to properly render + // Add a short delay to let the webview start the navigation DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.lastHandledVideoID = videoID self.isDuckPlayerTemporarilyDisabled = false @@ -224,6 +224,7 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Pixel for Views From SERP if let url = navigationAction.request.url, + if let url = navigdelayationAction.request.url, navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] == Constants.SERPURL, duckPlayer.settings.mode == .enabled, !url.isDuckPlayer { Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromSERP, debounce: 2) From 181ab0fb8d8ad9b0611011ca909183404ac94a7b Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 16:44:32 +0200 Subject: [PATCH 32/37] Fixed double if --- DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index c8b0f9e5e9..80082f4536 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -224,7 +224,6 @@ extension DuckPlayerNavigationHandler: DuckNavigationHandling { // Pixel for Views From SERP if let url = navigationAction.request.url, - if let url = navigdelayationAction.request.url, navigationAction.request.allHTTPHeaderFields?[Constants.refererHeader] == Constants.SERPURL, duckPlayer.settings.mode == .enabled, !url.isDuckPlayer { Pixel.fire(pixel: Pixel.Event.duckPlayerViewFromSERP, debounce: 2) From 8cf99c612c0ad0e6a55e3580a1e50bd7b7ccb7ce Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 16:46:13 +0200 Subject: [PATCH 33/37] Cleanup --- DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index 80082f4536..bf93b4dd01 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -28,8 +28,7 @@ final class DuckPlayerNavigationHandler { var duckPlayer: DuckPlayerProtocol var referrer: DuckPlayerReferrer = .other var lastHandledVideoID: String? - - var isDuckPlayerTemporarilyDisabled = false + var isDuckPlayerTemporarilyDisabled = false private struct Constants { static let SERPURL = "https://duckduckgo.com/" From b837c2b49436471ce13bdf5c679ea12f47966e40 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 16:58:19 +0200 Subject: [PATCH 34/37] Fix incorrect merge --- DuckDuckGo/TabViewController.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/DuckDuckGo/TabViewController.swift b/DuckDuckGo/TabViewController.swift index 8429839b37..5f3aa279e8 100644 --- a/DuckDuckGo/TabViewController.swift +++ b/DuckDuckGo/TabViewController.swift @@ -348,9 +348,6 @@ class TabViewController: UIViewController { subscribeToEmailProtectionSignOutNotification() registerForDownloadsNotifications() registerForAddressBarLocationNotifications() - - // Setup DuckPlayer navigation handler - self.youtubeNavigationHandler = YoutubePlayerNavigationHandler(duckPlayer: duckPlayer) if #available(iOS 16.4, *) { registerForInspectableWebViewNotifications() From 011d6c2c41125e29bb231ab3011bf8104f6f31b3 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 17:42:59 +0200 Subject: [PATCH 35/37] Tests are now passing --- .../DuckPlayerURLExtensionTests.swift | 50 +++++++++++-------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift b/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift index d5df8b9603..4cbf27c40c 100644 --- a/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift +++ b/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift @@ -22,6 +22,12 @@ import os.log @testable import DuckDuckGo final class DuckPlayerURLExtensionTests: XCTestCase { + + #if os(iOS) + let baseUrl = "https://m.youtube.com/" + #else + let baseUrl = "https://www.youtube.com/" + #endif func testIsDuckPlayerScheme() { XCTAssertTrue("duck:player/abcdef12345".url!.isDuckURLScheme) @@ -29,7 +35,7 @@ final class DuckPlayerURLExtensionTests: XCTestCase { XCTAssertTrue("duck://player/abcdef".url!.isDuckURLScheme) XCTAssertTrue("duck://player/12345".url!.isDuckURLScheme) XCTAssertFalse("http://duckplayer/abcdef12345".url!.isDuckURLScheme) - XCTAssertFalse("https://www.youtube.com/watch?v=abcdef12345".url!.isDuckURLScheme) + XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345".url!.isDuckURLScheme) XCTAssertFalse("https://www.youtube-nocookie.com/embed/abcdef12345".url!.isDuckURLScheme) } @@ -41,25 +47,25 @@ final class DuckPlayerURLExtensionTests: XCTestCase { XCTAssertFalse("https://www.youtube-nocookie.com/embed?t=23s".url!.isDuckPlayer) XCTAssertTrue("duck://player/abcdef12345".url!.isDuckPlayer) - XCTAssertFalse("https://www.youtube.com/watch?v=abcdef12345".url!.isDuckPlayer) + XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345".url!.isDuckPlayer) XCTAssertFalse("https://duckduckgo.com".url!.isDuckPlayer) } func testIsYoutubePlaylist() { - XCTAssertTrue("https://www.youtube.com/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubePlaylist) - XCTAssertTrue("https://www.youtube.com/watch?list=abcdefgh12345678&v=abcdef12345".url!.isYoutubePlaylist) + XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubePlaylist) + XCTAssertTrue("\(baseUrl)/watch?list=abcdefgh12345678&v=abcdef12345".url!.isYoutubePlaylist) XCTAssertFalse("https://duckduckgo.com/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubePlaylist) - XCTAssertFalse("https://www.youtube.com/watch?list=abcdefgh12345678".url!.isYoutubePlaylist) - XCTAssertFalse("https://www.youtube.com/watch?v=abcdef12345&list=abcdefgh12345678&index=1".url!.isYoutubePlaylist) + XCTAssertFalse("\(baseUrl)/watch?list=abcdefgh12345678".url!.isYoutubePlaylist) + XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678&index=1".url!.isYoutubePlaylist) } func testIsYoutubeVideo() { - XCTAssertTrue("https://www.youtube.com/watch?v=abcdef12345".url!.isYoutubeVideo) - XCTAssertTrue("https://www.youtube.com/watch?v=abcdef12345&list=abcdefgh12345678&index=1".url!.isYoutubeVideo) - XCTAssertTrue("https://www.youtube.com/watch?v=abcdef12345&t=5m".url!.isYoutubeVideo) + XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345".url!.isYoutubeVideo) + XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678&index=1".url!.isYoutubeVideo) + XCTAssertTrue("\(baseUrl)/watch?v=abcdef12345&t=5m".url!.isYoutubeVideo) - XCTAssertFalse("https://www.youtube.com/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubeVideo) + XCTAssertFalse("\(baseUrl)/watch?v=abcdef12345&list=abcdefgh12345678".url!.isYoutubeVideo) XCTAssertFalse("https://duckduckgo.com/watch?v=abcdef12345".url!.isYoutubeVideo) } @@ -74,15 +80,15 @@ final class DuckPlayerURLExtensionTests: XCTestCase { } func testYoutubeVideoParamsFromYoutubeURL() { - let params = "https://www.youtube.com/watch?v=abcdef12345".url!.youtubeVideoParams + let params = "\(baseUrl)/watch?v=abcdef12345".url!.youtubeVideoParams XCTAssertEqual(params?.videoID, "abcdef12345") XCTAssertEqual(params?.timestamp, nil) - let paramsWithTimestamp = "https://www.youtube.com/watch?v=abcdef12345&t=23s".url!.youtubeVideoParams + let paramsWithTimestamp = "\(baseUrl)watch?v=abcdef12345&t=23s".url!.youtubeVideoParams XCTAssertEqual(paramsWithTimestamp?.videoID, "abcdef12345") XCTAssertEqual(paramsWithTimestamp?.timestamp, "23s") - let paramsWithTimestampWithoutUnits = "https://www.youtube.com/watch?t=102&v=abcdef12345&feature=youtu.be".url!.youtubeVideoParams + let paramsWithTimestampWithoutUnits = "\(baseUrl)/watch?t=102&v=abcdef12345&feature=youtu.be".url!.youtubeVideoParams XCTAssertEqual(paramsWithTimestampWithoutUnits?.videoID, "abcdef12345") XCTAssertEqual(paramsWithTimestampWithoutUnits?.timestamp, "102") } @@ -110,15 +116,15 @@ final class DuckPlayerURLExtensionTests: XCTestCase { } func testYoutubeURLTimestampValidation() { - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: nil).absoluteString, "https://www.youtube.com/watch?v=abcdef12345") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "23s").absoluteString, "https://www.youtube.com/watch?v=abcdef12345&t=23s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5s").absoluteString, "https://www.youtube.com/watch?v=abcdef12345&t=5m5s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h400m100s").absoluteString, "https://www.youtube.com/watch?v=abcdef12345&t=12h400m100s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h2s2h").absoluteString, "https://www.youtube.com/watch?v=abcdef12345&t=12h2s2h") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5m5m").absoluteString, "https://www.youtube.com/watch?v=abcdef12345&t=5m5m5m") - - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5").absoluteString, "https://www.youtube.com/watch?v=abcdef12345&t=5") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "10d").absoluteString, "https://www.youtube.com/watch?v=abcdef12345") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: nil).absoluteString, "\(baseUrl)watch?v=abcdef12345") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "23s").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=23s") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5s").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=5m5s") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h400m100s").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=12h400m100s") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h2s2h").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=12h2s2h") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5m5m").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=5m5m5m") + + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "10d").absoluteString, "\(baseUrl)/watch?v=abcdef12345") } func testYoutubeNoCookieURLTimestampValidation() { From 872138e981614bd8a19b022c1172d3157b721b27 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 17:44:54 +0200 Subject: [PATCH 36/37] Remove / from path --- .../DuckPlayerURLExtensionTests.swift | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift b/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift index 4cbf27c40c..d45e6ca8f7 100644 --- a/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift +++ b/DuckDuckGoTests/DuckPlayerURLExtensionTests.swift @@ -24,9 +24,9 @@ import os.log final class DuckPlayerURLExtensionTests: XCTestCase { #if os(iOS) - let baseUrl = "https://m.youtube.com/" + let baseUrl = "https://m.youtube.com" #else - let baseUrl = "https://www.youtube.com/" + let baseUrl = "https://www.youtube.com" #endif func testIsDuckPlayerScheme() { @@ -84,7 +84,7 @@ final class DuckPlayerURLExtensionTests: XCTestCase { XCTAssertEqual(params?.videoID, "abcdef12345") XCTAssertEqual(params?.timestamp, nil) - let paramsWithTimestamp = "\(baseUrl)watch?v=abcdef12345&t=23s".url!.youtubeVideoParams + let paramsWithTimestamp = "\(baseUrl)/watch?v=abcdef12345&t=23s".url!.youtubeVideoParams XCTAssertEqual(paramsWithTimestamp?.videoID, "abcdef12345") XCTAssertEqual(paramsWithTimestamp?.timestamp, "23s") @@ -116,12 +116,12 @@ final class DuckPlayerURLExtensionTests: XCTestCase { } func testYoutubeURLTimestampValidation() { - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: nil).absoluteString, "\(baseUrl)watch?v=abcdef12345") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "23s").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=23s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5s").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=5m5s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h400m100s").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=12h400m100s") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h2s2h").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=12h2s2h") - XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5m5m").absoluteString, "\(baseUrl)watch?v=abcdef12345&t=5m5m5m") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: nil).absoluteString, "\(baseUrl)/watch?v=abcdef12345") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "23s").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=23s") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5s").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5m5s") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h400m100s").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=12h400m100s") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "12h2s2h").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=12h2s2h") + XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5m5m5m").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5m5m5m") XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "5").absoluteString, "\(baseUrl)/watch?v=abcdef12345&t=5") XCTAssertEqual(URL.youtube("abcdef12345", timestamp: "10d").absoluteString, "\(baseUrl)/watch?v=abcdef12345") From 0bd9aff704c3fbb74dedf7dd6f82048d49d2ffcb Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Fri, 19 Jul 2024 18:13:38 +0200 Subject: [PATCH 37/37] Update tests --- DuckDuckGoTests/DuckPlayerMocks.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/DuckDuckGoTests/DuckPlayerMocks.swift b/DuckDuckGoTests/DuckPlayerMocks.swift index ed84be6f1d..09d95354a8 100644 --- a/DuckDuckGoTests/DuckPlayerMocks.swift +++ b/DuckDuckGoTests/DuckPlayerMocks.swift @@ -110,6 +110,18 @@ final class MockDuckPlayerSettings: DuckPlayerSettingsProtocol { } final class MockDuckPlayer: DuckPlayerProtocol { + var hostView: UIViewController? + + func openDuckPlayerSettings(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + + func openDuckPlayerInfo(params: Any, message: WKScriptMessage) async -> (any Encodable)? { + nil + } + + func setHostViewController(_ vc: UIViewController) {} + func initialSetupPlayer(params: Any, message: WKScriptMessage) async -> (any Encodable)? { nil }