@@ -63,7 +63,11 @@ final class HomepageViewController: UIViewController,
63
63
private let overlayManager : OverlayModeManager
64
64
private let logger : Logger
65
65
private let toastContainer : UIView
66
- private var alreadyTrackedItems = Set < HomepageItem > ( )
66
+
67
+ // Telemetry related
68
+ private var alreadyTrackedSections = Set < HomepageSection > ( )
69
+ private var alreadyTrackedTopSites = Set < HomepageItem > ( )
70
+ private let trackingImpressionsThrottler : ThrottleProtocol
67
71
68
72
// MARK: - Initializers
69
73
init ( windowUUID: WindowUUID ,
@@ -73,7 +77,8 @@ final class HomepageViewController: UIViewController,
73
77
statusBarScrollDelegate: StatusBarScrollDelegate ? = nil ,
74
78
toastContainer: UIView ,
75
79
notificationCenter: NotificationProtocol = NotificationCenter . default,
76
- logger: Logger = DefaultLogger . shared
80
+ logger: Logger = DefaultLogger . shared,
81
+ throttler: ThrottleProtocol = Throttler ( seconds: 0.5 )
77
82
) {
78
83
self . windowUUID = windowUUID
79
84
self . themeManager = themeManager
@@ -82,6 +87,7 @@ final class HomepageViewController: UIViewController,
82
87
self . statusBarScrollDelegate = statusBarScrollDelegate
83
88
self . toastContainer = toastContainer
84
89
self . logger = logger
90
+ self . trackingImpressionsThrottler = throttler
85
91
86
92
// FXIOS-11490: This should be refactored when we refactor CFR to adhere to Redux
87
93
let jumpBackInContextualViewProvider = ContextualHintViewProvider (
@@ -179,7 +185,7 @@ final class HomepageViewController: UIViewController,
179
185
180
186
override func viewDidDisappear( _ animated: Bool ) {
181
187
super. viewDidDisappear ( animated)
182
- alreadyTrackedItems . removeAll ( )
188
+ resetTrackedObjects ( )
183
189
}
184
190
185
191
override func viewWillTransition( to size: CGSize , with coordinator: UIViewControllerTransitionCoordinator ) {
@@ -297,9 +303,9 @@ final class HomepageViewController: UIViewController,
297
303
)
298
304
// FXIOS-11523 - Trigger impression when user opens homepage view new tab + scroll to top
299
305
if homepageState. shouldTriggerImpression {
300
- alreadyTrackedItems. removeAll ( )
301
- trackVisibleItemImpressions ( )
302
306
scrollToTop ( )
307
+ resetTrackedObjects ( )
308
+ trackVisibleItemImpressions ( )
303
309
}
304
310
}
305
311
@@ -769,7 +775,7 @@ final class HomepageViewController: UIViewController,
769
775
)
770
776
}
771
777
772
- private func dispatchOpenTopSitesAction ( at index: Int , config: TopSiteConfiguration ) {
778
+ private func dispatchTopSitesAction ( at index: Int , config: TopSiteConfiguration , actionType : ActionType ) {
773
779
let config = TopSitesTelemetryConfig (
774
780
isZeroSearch: homepageState. isZeroSearch,
775
781
position: index,
@@ -779,7 +785,7 @@ final class HomepageViewController: UIViewController,
779
785
TopSitesAction (
780
786
telemetryConfig: config,
781
787
windowUUID: self . windowUUID,
782
- actionType: TopSitesActionType . tapOnHomepageTopSitesCell
788
+ actionType: actionType
783
789
)
784
790
)
785
791
}
@@ -804,7 +810,11 @@ final class HomepageViewController: UIViewController,
804
810
visitType: . link
805
811
)
806
812
dispatchNavigationBrowserAction ( with: destination, actionType: NavigationBrowserActionType . tapOnCell)
807
- dispatchOpenTopSitesAction ( at: indexPath. item, config: config)
813
+ dispatchTopSitesAction (
814
+ at: indexPath. item,
815
+ config: config,
816
+ actionType: TopSitesActionType . tapOnHomepageTopSitesCell
817
+ )
808
818
case . jumpBackIn( let config) :
809
819
store. dispatch (
810
820
JumpBackInAction (
@@ -867,50 +877,53 @@ final class HomepageViewController: UIViewController,
867
877
)
868
878
}
869
879
870
- /// Want to handle tracking here to capture any cells about to viewed,
871
- /// but some cells do not get reconfigured so we add additional tracking detection with `trackVisibleImpressions`
872
- func collectionView(
873
- _ collectionView: UICollectionView ,
874
- willDisplay cell: UICollectionViewCell ,
875
- forItemAt indexPath: IndexPath
876
- ) {
877
- guard let item = dataSource? . itemIdentifier ( for: indexPath) else { return }
878
- handleTrackingItemImpression ( with: item, at: indexPath. item)
879
- }
880
-
881
- /// Used to track item impressions. If the user has seen the item on the homepage, we only record the impression once.
880
+ /// Used to track impressions. If the user has already seen the item on the homepage, we only record the impression once.
882
881
/// We want to track at initial seen as well as when users scrolls.
882
+ /// A throttle is added in order to capture what the users has seen. When we scroll to top programmatically,
883
+ /// the impressions were being tracked, but to match user's perspective, we add a throttle to delay.
884
+ /// Time complexity: O(n) due to iterating visible items.
883
885
private func trackVisibleItemImpressions( ) {
884
- guard let collectionView else {
885
- logger. log (
886
- " Homepage collectionview should not have been nil, unable to track impression " ,
887
- level: . warning,
888
- category: . homepage
889
- )
890
- return
891
- }
892
- for indexPath in collectionView. indexPathsForVisibleItems {
893
- guard let item = dataSource? . itemIdentifier ( for: indexPath) else { continue }
894
- handleTrackingItemImpression ( with: item, at: indexPath. item)
886
+ trackingImpressionsThrottler. throttle { [ weak self] in
887
+ guard let self else { return }
888
+ guard let collectionView else {
889
+ logger. log (
890
+ " Homepage collectionview should not have been nil, unable to track impression " ,
891
+ level: . warning,
892
+ category: . homepage
893
+ )
894
+ return
895
+ }
896
+ for indexPath in collectionView. indexPathsForVisibleItems {
897
+ guard let section = dataSource? . sectionIdentifier ( for: indexPath. section) ,
898
+ let item = dataSource? . itemIdentifier ( for: indexPath) else { continue }
899
+ handleTrackingImpressions ( for: section, with: item, at: indexPath. item)
900
+ }
895
901
}
896
902
}
897
903
898
- private func handleTrackingItemImpression( with item: HomepageItem , at index: Int ) {
899
- guard !alreadyTrackedItems. contains ( item) else { return }
900
- alreadyTrackedItems. insert ( item)
901
- if case . topSite( let config, _) = item {
902
- sendItemActionWithTelemetryExtras (
903
- item: item,
904
- actionType: HomepageActionType . itemSeen,
905
- topSitesTelemetryConfig: TopSitesTelemetryConfig (
906
- isZeroSearch: homepageState. isZeroSearch,
907
- position: index,
908
- topSiteConfiguration: config
909
- )
910
- )
911
- } else {
912
- sendItemActionWithTelemetryExtras ( item: item, actionType: HomepageActionType . itemSeen)
913
- }
904
+ /// We want to capture generic section impressions,
905
+ /// but we also need to handle capturing individual sponsored tiles impressions
906
+ private func handleTrackingImpressions( for section: HomepageSection , with item: HomepageItem , at index: Int ) {
907
+ handleTrackingTopSitesImpression ( for: item, at: index)
908
+ handleTrackingSectionImpression ( for: section, with: item)
909
+ }
910
+
911
+ private func handleTrackingTopSitesImpression( for item: HomepageItem , at index: Int ) {
912
+ guard !alreadyTrackedTopSites. contains ( item) else { return }
913
+ alreadyTrackedTopSites. insert ( item)
914
+ guard case . topSite( let config, _) = item else { return }
915
+ dispatchTopSitesAction ( at: index, config: config, actionType: TopSitesActionType . topSitesSeen)
916
+ }
917
+
918
+ private func handleTrackingSectionImpression( for section: HomepageSection , with item: HomepageItem ) {
919
+ guard !alreadyTrackedSections. contains ( section) else { return }
920
+ alreadyTrackedSections. insert ( section)
921
+ sendItemActionWithTelemetryExtras ( item: item, actionType: HomepageActionType . sectionSeen)
922
+ }
923
+
924
+ private func resetTrackedObjects( ) {
925
+ alreadyTrackedSections. removeAll ( )
926
+ alreadyTrackedTopSites. removeAll ( )
914
927
}
915
928
916
929
// MARK: - UIPopoverPresentationControllerDelegate - Context Hints (CFR)
0 commit comments