Skip to content

Commit 5af44ea

Browse files
mergify[bot]OrlaM
andauthored
Bugfix FXIOS-7874 [v120] homepage crash (#17577) (#17591)
* Fix top sites crash * Fix tests (cherry picked from commit 69e65b8) Co-authored-by: OrlaM <[email protected]>
1 parent d962940 commit 5af44ea

File tree

3 files changed

+45
-69
lines changed

3 files changed

+45
-69
lines changed

Client/Frontend/Home/TopSites/DataManagement/TopSitesDataAdaptor.swift

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,8 @@ protocol TopSitesDataAdaptor {
1919
/// In other words, the number of rows shown depends on the actual data and the user preference.
2020
var numberOfRows: Int { get }
2121

22-
/// Get top sites data, already calculated and ready to be shown to the user
22+
/// Get top sites data
2323
func getTopSitesData() -> [TopSite]
24-
25-
/// Calculate top site data
26-
/// This calculation is dependent on the number of tiles per row that is shown in the user interface.
27-
/// Top sites are composed of pinned sites, history, Contiles and Google top site.
28-
/// Google top site is always first, then comes the contiles, pinned sites and history top sites.
29-
/// We only add Google top site or Contiles if number of pins doesn't exceeds the available number shown of tiles.
30-
/// - Parameter numberOfTilesPerRow: The number of tiles per row shown to the user
31-
func recalculateTopSiteData(for numberOfTilesPerRow: Int)
3224
}
3325

3426
class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
@@ -40,6 +32,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
4032
private var historySites: [Site] = []
4133
private var contiles: [Contile] = []
4234

35+
private let maxTopSites = 4 * 14 // Max rows * max tiles on the largest screen plus some padding
4336
var notificationCenter: NotificationProtocol
4437
weak var delegate: TopSitesManagerDelegate?
4538
private let topSiteHistoryManager: TopSiteHistoryManager
@@ -82,12 +75,18 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
8275
}
8376

8477
func getTopSitesData() -> [TopSite] {
78+
recalculateTopSiteData()
8579
return topSites
8680
}
8781

88-
func recalculateTopSiteData(for numberOfTilesPerRow: Int) {
82+
/// Calculate top site data
83+
/// This calculation is dependent on the number of tiles per row that is shown in the user interface.
84+
/// Top sites are composed of pinned sites, history, Contiles and Google top site.
85+
/// Google top site is always first, then comes the contiles, pinned sites and history top sites.
86+
/// We only add Google top site or Contiles if number of pins doesn't exceeds the available number shown of tiles.
87+
private func recalculateTopSiteData() {
8988
var sites = historySites
90-
let availableSpaceCount = getAvailableSpaceCount(numberOfTilesPerRow: numberOfTilesPerRow)
89+
let availableSpaceCount = getAvailableSpaceCount(maxTopSites: maxTopSites)
9190
let shouldAddGoogle = shouldAddGoogle(availableSpaceCount: availableSpaceCount)
9291

9392
// Add Sponsored tile
@@ -115,7 +114,7 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
115114
loadTopSites()
116115

117116
dispatchGroup.notify(queue: dataQueue) { [weak self] in
118-
self?.recalculateTopSiteData(for: TopSitesDataAdaptorImplementation.defaultTopSitesRowCount)
117+
self?.recalculateTopSiteData()
119118
self?.delegate?.didLoadNewData()
120119
dataLoadingCompletion?()
121120
}
@@ -151,10 +150,9 @@ class TopSitesDataAdaptorImplementation: TopSitesDataAdaptor, FeatureFlaggable {
151150
/// Get available space count for the sponsored tiles and Google tiles
152151
/// - Parameter numberOfTilesPerRow: Comes from top sites view model and accounts for different layout (landscape, portrait, iPhone, iPad, etc).
153152
/// - Returns: The available space count for the rest of the calculation
154-
private func getAvailableSpaceCount(numberOfTilesPerRow: Int) -> Int {
153+
private func getAvailableSpaceCount(maxTopSites: Int) -> Int {
155154
let pinnedSiteCount = countPinnedSites(sites: historySites)
156-
let totalNumberOfShownTiles = numberOfTilesPerRow * numberOfRows
157-
return totalNumberOfShownTiles - pinnedSiteCount
155+
return maxTopSites - pinnedSiteCount
158156
}
159157

160158
// The number of rows the user wants.

Client/Frontend/Home/TopSites/TopSitesViewModel.swift

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,11 @@ class TopSitesViewModel {
2222

2323
private let profile: Profile
2424
private var sentImpressionTelemetry = [String: Bool]()
25+
private var unfilteredTopSites: [TopSite] = []
2526
private var topSites: [TopSite] = []
2627
private let dimensionManager: TopSitesDimension
2728
private var numberOfItems: Int = 0
29+
private var numberOfRows: Int = 0
2830

2931
private let topSitesDataAdaptor: TopSitesDataAdaptor
3032
private let topSiteHistoryManager: TopSiteHistoryManager
@@ -165,7 +167,7 @@ extension TopSitesViewModel: HomepageViewModelProtocol, FeatureFlaggable {
165167

166168
let interface = TopSitesUIInterface(trait: traitCollection, availableWidth: size.width)
167169
let sectionDimension = dimensionManager.getSectionDimension(for: topSites,
168-
numberOfRows: topSitesDataAdaptor.numberOfRows,
170+
numberOfRows: numberOfRows,
169171
interface: interface)
170172
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize,
171173
subitem: item,
@@ -194,11 +196,14 @@ extension TopSitesViewModel: HomepageViewModelProtocol, FeatureFlaggable {
194196
let interface = TopSitesUIInterface(trait: traitCollection,
195197
availableWidth: size.width)
196198
let sectionDimension = dimensionManager.getSectionDimension(for: topSites,
197-
numberOfRows: topSitesDataAdaptor.numberOfRows,
199+
numberOfRows: numberOfRows,
198200
interface: interface)
199-
topSitesDataAdaptor.recalculateTopSiteData(for: sectionDimension.numberOfTilesPerRow)
200-
topSites = topSitesDataAdaptor.getTopSitesData()
201201
numberOfItems = sectionDimension.numberOfRows * sectionDimension.numberOfTilesPerRow
202+
topSites = unfilteredTopSites
203+
if numberOfItems < unfilteredTopSites.count {
204+
let range = numberOfItems..<unfilteredTopSites.count
205+
topSites.removeSubrange(range)
206+
}
202207
}
203208

204209
func screenWasShown() {
@@ -214,7 +219,9 @@ extension TopSitesViewModel: HomepageViewModelProtocol, FeatureFlaggable {
214219
extension TopSitesViewModel: TopSitesManagerDelegate {
215220
func didLoadNewData() {
216221
ensureMainThread {
217-
self.topSites = self.topSitesDataAdaptor.getTopSitesData()
222+
self.unfilteredTopSites = self.topSitesDataAdaptor.getTopSitesData()
223+
self.topSites = self.unfilteredTopSites
224+
self.numberOfRows = self.topSitesDataAdaptor.numberOfRows
218225
guard self.isEnabled else { return }
219226
self.delegate?.reloadView()
220227
}

Tests/ClientTests/Frontend/Home/TopSites/TopSitesDataAdaptorTests.swift

Lines changed: 20 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -57,21 +57,19 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
5757
func testCalculateTopSitesData_hasGoogleTopSite_googlePrefsNil() {
5858
let subject = createSubject()
5959

60-
subject.recalculateTopSiteData(for: 6)
60+
let data = subject.getTopSitesData()
6161

6262
// We test that without a pref, google is added
63-
let data = subject.getTopSitesData()
6463
XCTAssertTrue(data[0].isGoogleURL)
6564
XCTAssertTrue(data[0].isGoogleGUID)
6665
}
6766

6867
func testCalculateTopSitesData_hasGoogleTopSiteWithPinnedCount_googlePrefsNil() {
6968
let subject = createSubject(addPinnedSiteCount: 3)
7069

71-
subject.recalculateTopSiteData(for: 1)
70+
let data = subject.getTopSitesData()
7271

7372
// We test that without a pref, google is added even with pinned tiles
74-
let data = subject.getTopSitesData()
7573
XCTAssertTrue(data[0].isGoogleURL)
7674
XCTAssertTrue(data[0].isGoogleGUID)
7775
}
@@ -81,10 +79,9 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
8179
profile.prefs.setBool(true, forKey: PrefsKeys.GoogleTopSiteAddedKey)
8280
profile.prefs.setBool(true, forKey: PrefsKeys.GoogleTopSiteHideKey)
8381

84-
subject.recalculateTopSiteData(for: 1)
82+
let data = subject.getTopSitesData()
8583

8684
// We test that having more pinned than available tiles, google tile isn't put in
87-
let data = subject.getTopSitesData()
8885
XCTAssertFalse(data[0].isGoogleURL)
8986
XCTAssertFalse(data[0].isGoogleGUID)
9087
}
@@ -94,9 +91,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
9491
func testCalculateTopSitesData_pinnedSites() {
9592
let subject = createSubject(addPinnedSiteCount: 3)
9693

97-
subject.recalculateTopSiteData(for: 6)
98-
9994
let data = subject.getTopSitesData()
95+
10096
XCTAssertEqual(data.count, 14)
10197
XCTAssertTrue(data[0].isPinned)
10298
}
@@ -118,19 +114,17 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
118114
let expectedContileResult = ContileProviderMock.getContiles(contilesCount: 1)
119115
let subject = createSubject(expectedContileResult: ContileResult.success(expectedContileResult))
120116

121-
subject.recalculateTopSiteData(for: 6)
122-
123117
let data = subject.getTopSitesData()
118+
124119
XCTAssertEqual(data.count, 12, "Expects 1 google site, 1 contile, 10 history sites")
125120
}
126121

127122
func testCalculateTopSitesData_addSponsoredTileAfterGoogle() {
128123
let expectedContileResult = ContileProviderMock.getContiles(contilesCount: 1)
129124
let subject = createSubject(expectedContileResult: ContileResult.success(expectedContileResult))
130125

131-
subject.recalculateTopSiteData(for: 6)
132-
133126
let data = subject.getTopSitesData()
127+
134128
XCTAssertTrue(data[0].isGoogleURL)
135129
XCTAssertTrue(data[1].isSponsoredTile)
136130
XCTAssertFalse(data[2].isSponsoredTile)
@@ -140,9 +134,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
140134
let expectedContileResult = ContileResult.failure(ContileProvider.Error.noDataAvailable)
141135
let subject = createSubject(expectedContileResult: expectedContileResult)
142136

143-
subject.recalculateTopSiteData(for: 6)
144-
145137
let data = subject.getTopSitesData()
138+
146139
XCTAssertTrue(data[0].isGoogleURL)
147140
XCTAssertFalse(data[1].isSponsoredTile)
148141
XCTAssertFalse(data[2].isSponsoredTile)
@@ -152,9 +145,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
152145
let expectedContileResult = ContileResult.success([])
153146
let subject = createSubject(expectedContileResult: expectedContileResult)
154147

155-
subject.recalculateTopSiteData(for: 6)
156-
157148
let data = subject.getTopSitesData()
149+
158150
XCTAssertTrue(data[0].isGoogleURL)
159151
XCTAssertFalse(data[1].isSponsoredTile)
160152
XCTAssertFalse(data[2].isSponsoredTile)
@@ -165,8 +157,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
165157
let expectedContileResult = ContileProviderMock.getContiles(contilesCount: 3)
166158
let subject = createSubject(expectedContileResult: ContileResult.success(expectedContileResult))
167159

168-
subject.recalculateTopSiteData(for: 6)
169160
let data = subject.getTopSitesData()
161+
170162
XCTAssertTrue(data[0].isGoogleURL)
171163
XCTAssertTrue(data[1].isSponsoredTile)
172164
XCTAssertTrue(data[2].isSponsoredTile)
@@ -178,9 +170,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
178170
duplicateFirstTile: true,
179171
pinnedDuplicateTile: true)
180172
let subject = createSubject(addPinnedSiteCount: 1, expectedContileResult: ContileResult.success(expectedContileResult))
181-
subject.recalculateTopSiteData(for: 6)
182-
183173
let data = subject.getTopSitesData()
174+
184175
XCTAssertTrue(data[0].isGoogleURL)
185176
XCTAssertFalse(data[1].isSponsoredTile)
186177
XCTAssertFalse(data[2].isSponsoredTile)
@@ -190,10 +181,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
190181
let expectedContileResult = ContileProviderMock.getContiles(contilesCount: 1,
191182
duplicateFirstTile: true)
192183
let subject = createSubject(addPinnedSiteCount: 1, expectedContileResult: ContileResult.success(expectedContileResult))
193-
194-
subject.recalculateTopSiteData(for: 6)
195-
196184
let data = subject.getTopSitesData()
185+
197186
XCTAssertTrue(data[0].isGoogleURL)
198187
XCTAssertTrue(data[1].isSponsoredTile)
199188
XCTAssertFalse(data[2].isSponsoredTile)
@@ -204,10 +193,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
204193
duplicateFirstTile: true,
205194
pinnedDuplicateTile: true)
206195
let subject = createSubject(addPinnedSiteCount: 1, expectedContileResult: ContileResult.success(expectedContileResult))
207-
208-
subject.recalculateTopSiteData(for: 6)
209-
210196
let data = subject.getTopSitesData()
197+
211198
XCTAssertTrue(data[0].isGoogleURL)
212199
XCTAssertTrue(data[1].isSponsoredTile)
213200
XCTAssertEqual(data[1].title, ContileProviderMock.defaultSuccessData[0].name)
@@ -221,9 +208,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
221208
profile.prefs.setBool(true, forKey: PrefsKeys.GoogleTopSiteAddedKey)
222209
profile.prefs.setBool(true, forKey: PrefsKeys.GoogleTopSiteHideKey)
223210

224-
subject.recalculateTopSiteData(for: 6)
225-
226211
let data = subject.getTopSitesData()
212+
227213
XCTAssertFalse(data[0].isGoogleURL)
228214
XCTAssertFalse(data[1].isSponsoredTile)
229215
XCTAssertFalse(data[2].isSponsoredTile)
@@ -232,10 +218,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
232218
func testCalculateTopSitesData_doesNotAddTileIfAllSpacesArePinnedAndGoogleIsThere() {
233219
let expectedContileResult = ContileResult.success([])
234220
let subject = createSubject(addPinnedSiteCount: 11, expectedContileResult: expectedContileResult)
235-
236-
subject.recalculateTopSiteData(for: 6)
237-
238221
let data = subject.getTopSitesData()
222+
239223
XCTAssertTrue(data[0].isGoogleURL)
240224
XCTAssertFalse(data[1].isSponsoredTile)
241225
XCTAssertFalse(data[2].isSponsoredTile)
@@ -305,10 +289,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
305289
// Sponsored > Frequency
306290
func testDuplicates_SponsoredTileHasPrecedenceOnFrequencyTiles() {
307291
let subject = createSubject(expectedContileResult: ContileResult.success([ContileProviderMock.duplicateTile]))
308-
309-
subject.recalculateTopSiteData(for: 6)
310-
311292
let data = subject.getTopSitesData()
293+
312294
XCTAssertTrue(data[0].isGoogleURL)
313295
XCTAssertEqual(data[1].title, ContileProviderMock.duplicateTile.name)
314296
XCTAssertTrue(data[1].isSponsoredTile)
@@ -320,9 +302,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
320302
let subject = createSubject(addPinnedSiteCount: 1,
321303
expectedContileResult: ContileResult.success([ContileProviderMock.pinnedDuplicateTile]))
322304

323-
subject.recalculateTopSiteData(for: 6)
324-
325305
let data = subject.getTopSitesData()
306+
326307
XCTAssertTrue(data[0].isGoogleURL)
327308
XCTAssertFalse(data[1].isSponsoredTile)
328309
XCTAssertTrue(data[1].isPinned)
@@ -334,10 +315,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
334315
func testDuplicates_PinnedTilesHasPrecedenceOnFrequencyTiles() {
335316
let expectedPinnedURL = String(format: ContileProviderMock.url, "0")
336317
let subject = createSubject(addPinnedSiteCount: 1, siteCount: 3, duplicatePinnedSiteURL: true)
337-
338-
subject.recalculateTopSiteData(for: 6)
339-
340318
let data = subject.getTopSitesData()
319+
341320
XCTAssertEqual(data.count, 4, "Should have 3 sites and 1 pinned")
342321
XCTAssertTrue(data[0].isGoogleURL)
343322

@@ -361,9 +340,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
361340
func testDuplicates_PinnedTilesOfSameDomainIsntDeduped() {
362341
let subject = createSubject(addPinnedSiteCount: 2, siteCount: 0)
363342

364-
subject.recalculateTopSiteData(for: 6)
365-
366343
let data = subject.getTopSitesData()
344+
367345
XCTAssertEqual(data.count, 3, "Should have google site and 2 pinned sites")
368346
XCTAssertTrue(data[0].isGoogleURL)
369347

@@ -388,8 +366,6 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
388366
isCustomEngine: false)
389367
add(searchEngine: googleSearchEngine)
390368
let subject = createSubject()
391-
subject.recalculateTopSiteData(for: 6)
392-
393369
let data = subject.getTopSitesData()
394370

395371
XCTAssertTrue(data[0].isGoogleURL)
@@ -405,9 +381,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
405381
isCustomEngine: false)
406382
add(searchEngine: pinnedTileSearchEngine)
407383
let subject = createSubject(addPinnedSiteCount: 3)
408-
subject.recalculateTopSiteData(for: 6)
409-
410384
let data = subject.getTopSitesData()
385+
411386
XCTAssertEqual(data.count, 14)
412387
XCTAssertTrue(data[0].isPinned)
413388
}
@@ -421,8 +396,6 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
421396
isCustomEngine: false)
422397
add(searchEngine: historyTileSearchEngine)
423398
let subject = createSubject()
424-
subject.recalculateTopSiteData(for: 6)
425-
426399
let data = subject.getTopSitesData()
427400

428401
XCTAssertEqual(data[1].title, "A title 0")
@@ -438,10 +411,8 @@ class TopSitesDataAdaptorTests: XCTestCase, FeatureFlaggable {
438411
add(searchEngine: sponsoredTileSearchEngine)
439412
let expectedContileResult = ContileProviderMock.getContiles(contilesCount: 1)
440413
let subject = createSubject(expectedContileResult: ContileResult.success(expectedContileResult))
441-
442-
subject.recalculateTopSiteData(for: 6)
443-
444414
let data = subject.getTopSitesData()
415+
445416
XCTAssertTrue(data[0].isGoogleURL)
446417
XCTAssertFalse(data[1].isSponsoredTile)
447418
}

0 commit comments

Comments
 (0)