Skip to content

Commit

Permalink
Merge branch 'mozilla-mobile:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
isabelrios authored Apr 25, 2024
2 parents 2d47ff6 + ae925e4 commit 9959262
Show file tree
Hide file tree
Showing 37 changed files with 388 additions and 229 deletions.
4 changes: 2 additions & 2 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -21576,15 +21576,15 @@
repositoryURL = "https://github.com/mozilla/rust-components-swift.git";
requirement = {
kind = exactVersion;
version = 127.0.20240417050328;
version = 127.0.20240424134628;
};
};
435C85EE2788F4D00072B526 /* XCRemoteSwiftPackageReference "glean-swift" */ = {
isa = XCRemoteSwiftPackageReference;
repositoryURL = "https://github.com/mozilla/glean-swift";
requirement = {
kind = exactVersion;
version = 59.0.0;
version = 60.0.0;
};
};
4368F83B279669690013419B /* XCRemoteSwiftPackageReference "SnapKit" */ = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mozilla/glean-swift",
"state" : {
"revision" : "fd5a73d20b519517a7840526266ef20901aedbcb",
"version" : "59.0.0"
"revision" : "227b9d80062eb6921963fac1899a25fff6babe02",
"version" : "60.0.0"
}
},
{
Expand Down Expand Up @@ -95,8 +95,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/mozilla/rust-components-swift.git",
"state" : {
"revision" : "6fc91097ce72934b00fe1fa22de2f0020bfa896d",
"version" : "127.0.20240417050328"
"revision" : "86be6a173c32ee2403f3cd61bbdb8c39c6e0152b",
"version" : "127.0.20240424134628"
}
},
{
Expand Down
8 changes: 6 additions & 2 deletions firefox-ios/Client/Application/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
guard !AppConstants.isRunningUnitTest else { return }

// Resume previously stopped downloads for, and on, THIS scene only.
downloadQueue.resumeAll()
if let uuid = sceneCoordinator?.windowUUID {
downloadQueue.resumeAll(for: uuid)
}
}

// MARK: - Transitioning to Background
Expand All @@ -80,7 +82,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
/// Use this method to reduce the scene's memory usage, clear claims to resources & dependencies / services.
/// UIKit takes a snapshot of the scene for the app switcher after this method returns.
func sceneDidEnterBackground(_ scene: UIScene) {
downloadQueue.pauseAll()
if let uuid = sceneCoordinator?.windowUUID {
downloadQueue.pauseAll(for: uuid)
}
}

// MARK: - Opening URLs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,6 @@ class BrowserCoordinator: BaseCoordinator,
self.glean = glean
super.init(router: router)

// TODO [7856]: Additional telemetry updates forthcoming once iPad multi-window enabled.
// For now, we only have a single BVC and TabManager. Plug it into our TelemetryWrapper:
TelemetryWrapper.shared.defaultTabManager = tabManager

browserViewController.browserDelegate = self
browserViewController.navigationHandler = self
tabManager.addDelegate(self)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import Shared

extension BrowserViewController: DownloadQueueDelegate {
func downloadQueue(_ downloadQueue: DownloadQueue, didStartDownload download: Download) {
// For now, each window handles its downloads independently; ignore any messages for other windows' downloads.
let uuid = windowUUID
guard download.originWindow == uuid else { return }

// If no other download toast is shown, create a new download toast and show it.
guard let downloadToast = self.downloadToast else {
let downloadToast = DownloadToast(download: download,
Expand All @@ -19,7 +23,7 @@ extension BrowserViewController: DownloadQueueDelegate {

// Handle download cancellation
if buttonPressed, !downloadQueue.isEmpty {
downloadQueue.cancelAll()
downloadQueue.cancelAll(for: uuid)

SimpleToast().showAlertWithText(.DownloadCancelledToastLabelText,
bottomContainer: self.contentContainer,
Expand Down Expand Up @@ -47,12 +51,14 @@ extension BrowserViewController: DownloadQueueDelegate {

func downloadQueue(_ downloadQueue: DownloadQueue, didCompleteWithError error: Error?) {
guard let downloadToast = self.downloadToast,
let download = downloadToast.downloads.first
let download = downloadToast.downloads.first,
download.originWindow == windowUUID
else { return }

DispatchQueue.main.async {
downloadToast.dismiss(false)

// We only care about download errors specific to our window's downloads
if error == nil {
let viewModel = ButtonToastViewModel(labelText: download.filename,
imageName: StandardImageIdentifiers.Large.checkmark,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ extension BrowserViewController: WKNavigationDelegate {
}

// Open our helper and cancel this response from the webview.
if let downloadViewModel = downloadHelper.downloadViewModel(okAction: downloadAction) {
if let downloadViewModel = downloadHelper.downloadViewModel(windowUUID: windowUUID,
okAction: downloadAction) {
presentSheetWith(viewModel: downloadViewModel, on: self, from: urlBar)
}
decisionHandler(.cancel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ class BrowserViewController: UIViewController,
screenshotHelper = ScreenshotHelper(controller: self)
tabManager.addDelegate(self)
tabManager.addNavigationDelegate(self)
downloadQueue.delegate = self
downloadQueue.addDelegate(self)
let tabWindowUUID = tabManager.windowUUID
AppEventQueue.wait(for: [.startupFlowComplete, .tabRestoration(tabWindowUUID)]) { [weak self] in
// Ensure we call into didBecomeActive at least once during startup flow (if needed)
Expand Down Expand Up @@ -501,7 +501,7 @@ class BrowserViewController: UIViewController,
private func nightModeUpdates() {
if NightModeHelper.isActivated(),
!featureFlags.isFeatureEnabled(.nightMode, checking: .buildOnly) {
NightModeHelper.turnOff(tabManager: tabManager)
NightModeHelper.turnOff()
themeManager.reloadTheme(for: windowUUID)
}

Expand Down
124 changes: 89 additions & 35 deletions firefox-ios/Client/Frontend/Browser/DownloadQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import Foundation
import WebKit
import Shared

protocol DownloadDelegate: AnyObject {
func download(_ download: Download, didCompleteWithError error: Error?)
Expand All @@ -16,16 +17,18 @@ class Download: NSObject {

fileprivate(set) var filename: String
fileprivate(set) var mimeType: String
let originWindow: WindowUUID

fileprivate(set) var isComplete = false

fileprivate(set) var totalBytesExpected: Int64?
fileprivate(set) var bytesDownloaded: Int64

override init() {
init(originWindow: WindowUUID) {
self.filename = "unknown"
self.mimeType = "application/octet-stream"

self.originWindow = originWindow
self.bytesDownloaded = 0

super.init()
Expand Down Expand Up @@ -83,7 +86,10 @@ class HTTPDownload: Download {
return string.components(separatedBy: allowed.inverted).joined()
}

init?(cookieStore: WKHTTPCookieStore, preflightResponse: URLResponse, request: URLRequest) {
init?(originWindow: WindowUUID,
cookieStore: WKHTTPCookieStore,
preflightResponse: URLResponse,
request: URLRequest) {
self.cookieStore = cookieStore
self.preflightResponse = preflightResponse

Expand All @@ -99,7 +105,7 @@ class HTTPDownload: Download {
guard let scheme = self.request.url?.scheme else { return nil }
guard scheme == "http" || scheme == "https" else { return nil }

super.init()
super.init(originWindow: originWindow)

if let filename = preflightResponse.suggestedFilename {
self.filename = HTTPDownload.stripUnicode(fromFilename: filename)
Expand Down Expand Up @@ -182,10 +188,10 @@ extension HTTPDownload: URLSessionTaskDelegate, URLSessionDownloadDelegate {
class BlobDownload: Download {
private let data: Data

init(filename: String, mimeType: String, size: Int64, data: Data) {
init(originWindow: WindowUUID, filename: String, mimeType: String, size: Int64, data: Data) {
self.data = data

super.init()
super.init(originWindow: originWindow)

self.filename = filename
self.mimeType = mimeType
Expand All @@ -211,6 +217,7 @@ class BlobDownload: Download {
}

protocol DownloadQueueDelegate: AnyObject {
var windowUUID: WindowUUID { get }
func downloadQueue(_ downloadQueue: DownloadQueue, didStartDownload download: Download)
func downloadQueue(
_ downloadQueue: DownloadQueue,
Expand All @@ -221,94 +228,141 @@ protocol DownloadQueueDelegate: AnyObject {
func downloadQueue(_ downloadQueue: DownloadQueue, didCompleteWithError error: Error?)
}

class WeakDownloadQueueDelegate {
private(set) weak var delegate: DownloadQueueDelegate?
init(delegate: DownloadQueueDelegate? = nil) { self.delegate = delegate }
}

struct DownloadProgress {
var bytesDownloaded: Int64
var totalExpected: Int64
}

class DownloadQueue {
var downloads: [Download]

weak var delegate: DownloadQueueDelegate?
private var delegates = [WeakDownloadQueueDelegate]()

var isEmpty: Bool {
return downloads.isEmpty
}

fileprivate var combinedBytesDownloaded: Int64 = 0
fileprivate var combinedTotalBytesExpected: Int64?
fileprivate var lastDownloadError: Error?
fileprivate var downloadProgress: [WindowUUID: DownloadProgress] = [:]
fileprivate var downloadErrors: [WindowUUID: Error] = [:]

init() {
self.downloads = []
}

func addDelegate(_ delegate: DownloadQueueDelegate) {
self.cleanUpDelegates()
delegates.append(WeakDownloadQueueDelegate(delegate: delegate))
}

func removeDelegate(_ delegate: DownloadQueueDelegate) {
delegates.removeAll(where: { $0.delegate === delegate || $0.delegate == nil })
}

func enqueue(_ download: Download) {
// Clear the download stats if the queue was empty at the start.
if downloads.isEmpty {
combinedBytesDownloaded = 0
combinedTotalBytesExpected = 0
lastDownloadError = nil
let uuid = download.originWindow
if downloads(for: uuid).isEmpty {
downloadProgress[uuid] = nil
downloadErrors[uuid] = nil
}

downloads.append(download)
download.delegate = self

if let totalBytesExpected = download.totalBytesExpected, combinedTotalBytesExpected != nil {
combinedTotalBytesExpected! += totalBytesExpected
if let totalBytesExpected = download.totalBytesExpected {
var progress = downloadProgress[uuid] ?? DownloadProgress(bytesDownloaded: 0, totalExpected: 0)
progress.totalExpected += totalBytesExpected
downloadProgress[uuid] = progress
} else {
combinedTotalBytesExpected = nil
downloadProgress[uuid] = nil
}

download.resume()
delegate?.downloadQueue(self, didStartDownload: download)
delegates.forEach { $0.delegate?.downloadQueue(self, didStartDownload: download) }
}

func cancelAll() {
for download in downloads where !download.isComplete {
func cancelAll(for window: WindowUUID) {
for download in downloads where !download.isComplete && download.originWindow == window {
download.cancel()
}
}

func pauseAll() {
for download in downloads where !download.isComplete {
func pauseAll(for window: WindowUUID) {
for download in downloads where !download.isComplete && download.originWindow == window {
download.pause()
}
}

func resumeAll() {
for download in downloads where !download.isComplete {
func resumeAll(for window: WindowUUID) {
for download in downloads where !download.isComplete && download.originWindow == window {
download.resume()
}
}

// MARK: - Utility

private func cleanUpDelegates() {
delegates.removeAll(where: { $0.delegate == nil })
}

private func downloads(for window: WindowUUID) -> [Download] {
return downloads.filter({ $0.originWindow == window })
}
}

extension DownloadQueue: DownloadDelegate {
func download(_ download: Download, didCompleteWithError error: Error?) {
guard let error = error, let index = downloads.firstIndex(of: download) else { return }

lastDownloadError = error
let uuid = download.originWindow
downloadErrors[uuid] = error
downloads.remove(at: index)

if downloads.isEmpty {
delegate?.downloadQueue(self, didCompleteWithError: lastDownloadError)
// If all downloads for the completed download's window are completed, we notify of error
if downloads(for: uuid).isEmpty {
delegates.forEach {
guard $0.delegate?.windowUUID == uuid else { return }
$0.delegate?.downloadQueue(self, didCompleteWithError: error)
}
}
}

func download(_ download: Download, didDownloadBytes bytesDownloaded: Int64) {
combinedBytesDownloaded += bytesDownloaded
delegate?.downloadQueue(
self,
didDownloadCombinedBytes: combinedBytesDownloaded,
combinedTotalBytesExpected: combinedTotalBytesExpected
)
let uuid = download.originWindow
var progress = downloadProgress[uuid] ?? DownloadProgress(bytesDownloaded: 0, totalExpected: 0)
progress.bytesDownloaded += bytesDownloaded
downloadProgress[uuid] = progress

delegates.forEach {
guard $0.delegate?.windowUUID == uuid else { return }
$0.delegate?.downloadQueue(self,
didDownloadCombinedBytes: progress.bytesDownloaded,
combinedTotalBytesExpected: progress.totalExpected)
}
}

func download(_ download: Download, didFinishDownloadingTo location: URL) {
guard let index = downloads.firstIndex(of: download) else { return }

downloads.remove(at: index)
delegate?.downloadQueue(self, download: download, didFinishDownloadingTo: location)
delegates.forEach { $0.delegate?.downloadQueue(self, download: download, didFinishDownloadingTo: location) }

NotificationCenter.default.post(name: .FileDidDownload, object: location)

if downloads.isEmpty {
delegate?.downloadQueue(self, didCompleteWithError: lastDownloadError)
// If all downloads for the completed download's window are completed, we notify of completion
let uuid = download.originWindow
if downloads(for: uuid).isEmpty {
let error = downloadErrors[uuid]
delegates.forEach {
guard $0.delegate?.windowUUID == uuid else { return }
$0.delegate?.downloadQueue(self, didCompleteWithError: error)
}
downloadErrors[uuid] = nil
}
}
}
Loading

0 comments on commit 9959262

Please sign in to comment.