-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Lazy load background tabs at app startup (#553)
Task/Issue URL: https://app.asana.com/0/1177771139624306/1201065943414949/f Description: I added a mechanism that records selected tabs in chronological order and reloads them in that order at app startup. The logic is as follows: * app launches * if there are no URL tabs or only 1 URL tab and it's currently selected, do nothing, don't even initialize * if current tab is a URL tab, wait until it finishes loading (or fails to load), otherwise proceed immediately * pick up to 3 most recently visited URL tab and reload them in background * as background tabs finish (or fail) loading, repeat previous step (keeping at most 3 concurrent loads) until 20 background tabs are loaded or there are no more URL tabs to load * as the user switches through tabs during lazy loading, record visited tabs and remove them from the lazy loading queue (they were visited manually which triggered a reload) * if 20 tabs were reloaded in background or there are no more not activated URL tabs, report finished work Additional details: * TabLazyLoader is owned by TabCollectionViewModel which serves as its data source * When lazy loader reports completion, it gets deallocated * Lazy loader does not differentiate between successful and failed loads, it only records website load attempts and completion (regardless of the outcome) - it's therefore not affected by "no internet" scenario * the implementation is based on generic types to allow for more thorough unit testing * the mechanism is inspired by how Chromium-based browsers work, but magic numbers 3 and 20 mentioned above were picked by Gabriel - you'll find more information in the Asana task. * OSLog.tabLazyLoading was added to help debugging lazy loading (disabled by default) * If there are more than 20 tabs at app startup, lazy loading starts with 10 tabs adjacent to the current one, before proceeding to 10 most recently selected. * Lazy loading is paused every time the user interacts with the current tab (triggers a navigation).
- Loading branch information
Showing
13 changed files
with
1,053 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
DuckDuckGo/Common/Utilities/AdjacentItemEnumerator.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
// | ||
// AdjacentItemEnumerator.swift | ||
// | ||
// Copyright © 2022 DuckDuckGo. All rights reserved. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
// | ||
|
||
import Foundation | ||
|
||
/** | ||
* This struct generates array indices adjacent to a given index. | ||
* | ||
* The _adjacent_ indices are generated as diffs against given index | ||
* with the following pattern: | ||
* | ||
* 1, -1, 2, -2, 3, -3, 4, -4, ... | ||
* | ||
*/ | ||
struct AdjacentItemEnumerator { | ||
|
||
/** | ||
* The index of an item for which adjacent items indices are computed. | ||
*/ | ||
var itemIndex: Int | ||
|
||
/** | ||
* Last valid adjacent item index returned by `nextIndex(arraySize:)`. | ||
*/ | ||
var currentAdjacentIndex: Int { | ||
itemIndex + currentDiff | ||
} | ||
|
||
/** | ||
* Computes next adjacent index, constrained by `arraySize`. | ||
* | ||
* Returns the next available and valid index, between by `0` and `arraySize-1`, | ||
* or `nil` if the index falls outside array bounds. | ||
*/ | ||
mutating func nextIndex(arraySize: Int) -> Int? { | ||
if currentDiff != 0 { | ||
operation = operation.next | ||
} | ||
|
||
previousDiff = currentDiff | ||
currentDiff = operation.perform(with: currentDiff) | ||
|
||
let newIndex = itemIndex + currentDiff | ||
|
||
if newIndex < 0 || newIndex >= arraySize { | ||
let previousIndex = itemIndex + previousDiff | ||
if previousIndex <= 0 || previousIndex >= arraySize - 1 { | ||
return nil | ||
} | ||
return nextIndex(arraySize: arraySize) | ||
} | ||
return newIndex | ||
} | ||
|
||
/** | ||
* Resets the enumerator internal state | ||
* | ||
* Calling this function is equivalent to reinstantiating the enumerator. | ||
*/ | ||
mutating func reset() { | ||
currentDiff = 0 | ||
previousDiff = 0 | ||
operation = .toggleSignAndAdvance | ||
} | ||
|
||
init(itemIndex: Int = 0) { | ||
self.itemIndex = itemIndex | ||
} | ||
|
||
// MARK: - Private | ||
|
||
private var currentDiff = 0 | ||
private var previousDiff = 0 | ||
private var operation: Operation = .toggleSignAndAdvance | ||
|
||
private enum Operation { | ||
case toggleSign, toggleSignAndAdvance | ||
|
||
func perform(with value: Int) -> Int { | ||
switch self { | ||
case .toggleSign: | ||
return value * -1 | ||
case .toggleSignAndAdvance: | ||
return (value * -1) + 1 | ||
} | ||
} | ||
|
||
var next: Operation { | ||
switch self { | ||
case .toggleSign: | ||
return .toggleSignAndAdvance | ||
case .toggleSignAndAdvance: | ||
return .toggleSign | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.