From 2daac150da5f6c0bbce8c0160efdafacf900fd40 Mon Sep 17 00:00:00 2001 From: Daniel Bernal Date: Mon, 11 Nov 2024 19:36:55 +0100 Subject: [PATCH] Base Pixel Implementation Implement pixels for youtubeOverlay behavior --- .../DuckPlayerNavigationHandler.swift | 18 +- .../DuckPlayerOverlayUsagePixels.swift | 98 +++---- DuckDuckGo/TabViewController.swift | 2 +- .../DuckPlayerOverlayUsagePixelsTests.swift | 247 ++++++------------ 4 files changed, 124 insertions(+), 241 deletions(-) diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift index f64a61f558..dc00d0cc3d 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerNavigationHandler.swift @@ -117,7 +117,7 @@ final class DuckPlayerNavigationHandler: NSObject { pixelFiring: PixelFiring.Type = Pixel.self, dailyPixelFiring: DailyPixelFiring.Type = DailyPixel.self, tabNavigationHandler: DuckPlayerTabNavigationHandling? = nil, - duckPlayerOverlayUsagePixels: DuckPlayerOverlayPixelFiring? = nil) { + duckPlayerOverlayUsagePixels: DuckPlayerOverlayPixelFiring? = DuckPlayerOverlayUsagePixels()) { self.duckPlayer = duckPlayer self.featureFlagger = featureFlagger self.appSettings = appSettings @@ -575,9 +575,7 @@ final class DuckPlayerNavigationHandler: NSObject { // Watch in YT videos always open in new tab redirectToYouTubeVideo(url: url, webView: webView, forceNewTab: true) } - - - } + } } @@ -663,9 +661,6 @@ extension DuckPlayerNavigationHandler: DuckPlayerNavigationHandling { @MainActor func handleURLChange(webView: WKWebView) -> DuckPlayerNavigationHandlerURLChangeResult { - // Track overlayUsagePixels - duckPlayerOverlayUsagePixels?.registerNavigation(url: webView.url) - // We want to prevent multiple simultaneous redirects // This can be caused by Duplicate Nav events, and quick URL changes if let lastTimestamp = lastURLChangeHandling, @@ -676,6 +671,11 @@ extension DuckPlayerNavigationHandler: DuckPlayerNavigationHandling { // Update the Referrer based on the first URL change detected setReferrer(webView: webView) + // Overlay Usage Pixel handling + if let url = webView.url { + duckPlayerOverlayUsagePixels?.handleNavigationAndFirePixels(url: url, duckPlayerMode: duckPlayerMode) + } + // We don't want YouTube redirects happening while default navigation is happening // This can be caused by Duplicate Nav events, and quick URL changes if let lastTimestamp = lastNavigationHandling, @@ -735,7 +735,7 @@ extension DuckPlayerNavigationHandler: DuckPlayerNavigationHandling { @MainActor func handleGoBack(webView: WKWebView) { - guard isDuckPlayerFeatureEnabled else { + guard let url = webView.url, url.isDuckPlayer, isDuckPlayerFeatureEnabled else { webView.goBack() return } @@ -783,7 +783,7 @@ extension DuckPlayerNavigationHandler: DuckPlayerNavigationHandling { guard let url = webView.url else { return } - + if url.isDuckPlayer, duckPlayerMode != .disabled { redirectToDuckPlayerVideo(url: url, webView: webView, disableNewTab: true) return diff --git a/DuckDuckGo/DuckPlayer/DuckPlayerOverlayUsagePixels.swift b/DuckDuckGo/DuckPlayer/DuckPlayerOverlayUsagePixels.swift index 459b762f40..54c94f428e 100644 --- a/DuckDuckGo/DuckPlayer/DuckPlayerOverlayUsagePixels.swift +++ b/DuckDuckGo/DuckPlayer/DuckPlayerOverlayUsagePixels.swift @@ -24,14 +24,7 @@ protocol DuckPlayerOverlayPixelFiring { var pixelFiring: PixelFiring.Type { get set } var navigationHistory: [URL] { get set } - func registerNavigation(url: URL?) - func navigationBack(duckPlayerMode: DuckPlayerMode) - func navigationReload(duckPlayerMode: DuckPlayerMode) - func navigationWithinYoutube(duckPlayerMode: DuckPlayerMode) - func navigationOutsideYoutube(duckPlayerMode: DuckPlayerMode) - func navigationClosed(duckPlayerMode: DuckPlayerMode) - func overlayIdle(duckPlayerMode: DuckPlayerMode) - + func handleNavigationAndFirePixels(url: URL?, duckPlayerMode: DuckPlayerMode) } final class DuckPlayerOverlayUsagePixels: DuckPlayerOverlayPixelFiring { @@ -55,69 +48,56 @@ final class DuckPlayerOverlayUsagePixels: DuckPlayerOverlayPixelFiring { idleTimer = nil } - func registerNavigation(url: URL?) { + func handleNavigationAndFirePixels(url: URL?, duckPlayerMode: DuckPlayerMode) { guard let url = url else { return } - navigationHistory.append(url) - - // Cancel and reset the idle timer whenever a new navigation occurs - resetIdleTimer() - } + let comparisonURL = url.forComparison() - func navigationBack(duckPlayerMode: DuckPlayerMode) { - guard duckPlayerMode == .alwaysAsk, - let lastURL = navigationHistory.last, - lastURL.isYoutubeWatch else { return } - - pixelFiring.fire(.duckPlayerYouTubeOverlayNavigationBack, withAdditionalParameters: [:]) - } + // Only append the URL if it's different from the last entry in normalized form + navigationHistory.append(comparisonURL) - func navigationReload(duckPlayerMode: DuckPlayerMode) { - guard duckPlayerMode == .alwaysAsk, - let lastURL = navigationHistory.last, - lastURL.isYoutubeWatch else { return } - - pixelFiring.fire(.duckPlayerYouTubeOverlayNavigationRefresh, withAdditionalParameters: [:]) - } - - func navigationWithinYoutube(duckPlayerMode: DuckPlayerMode) { + // DuckPlayer is in Ask Mode, there's navigation history, and last URL is a YouTube Watch Video guard duckPlayerMode == .alwaysAsk, navigationHistory.count > 1, let currentURL = navigationHistory.last, let previousURL = navigationHistory.dropLast().last, - previousURL.isYoutubeWatch, - currentURL.isYoutube else { return } + previousURL.isYoutubeWatch else { return } - pixelFiring.fire(.duckPlayerYouTubeNavigationWithinYouTube, withAdditionalParameters: [:]) - } + var isReload = false + // Check for a reload condition: when current videoID is the same as Previous + if let currentVideoID = currentURL.youtubeVideoParams?.videoID, + let previousVideoID = previousURL.youtubeVideoParams?.videoID { + isReload = currentVideoID == previousVideoID + } - func navigationOutsideYoutube(duckPlayerMode: DuckPlayerMode) { - guard duckPlayerMode == .alwaysAsk, - navigationHistory.count > 1, - let currentURL = navigationHistory.last, - let previousURL = navigationHistory.dropLast().last, - previousURL.isYoutubeWatch, - !currentURL.isYoutube else { return } + // Fire the reload pixel if this is a reload navigation + if isReload { + pixelFiring.fire(.duckPlayerYouTubeOverlayNavigationRefresh, withAdditionalParameters: [:]) + } else { + // Determine if it’s a back navigation by looking further back in history + let isBackNavigation = navigationHistory.count > 2 && + navigationHistory[navigationHistory.count - 3].forComparison() == currentURL.forComparison() + + // Fire the appropriate pixel based on navigation type + if isBackNavigation { + pixelFiring.fire(.duckPlayerYouTubeOverlayNavigationBack, withAdditionalParameters: [:]) + } else if previousURL.isYoutubeWatch && currentURL.isYoutube { + // Forward navigation within YouTube (including non-video URLs) + pixelFiring.fire(.duckPlayerYouTubeNavigationWithinYouTube, withAdditionalParameters: [:]) + } else if previousURL.isYoutubeWatch && !currentURL.isYoutube { + // Navigation outside YouTube + pixelFiring.fire(.duckPlayerYouTubeOverlayNavigationOutsideYoutube, withAdditionalParameters: [:]) + } + } - pixelFiring.fire(.duckPlayerYouTubeOverlayNavigationOutsideYoutube, withAdditionalParameters: [:]) - } + // Refined truncation logic: + if let lastOccurrenceIndex = (0..