Skip to content

Commit

Permalink
Fix brave/brave-ios#7574: Add support for multi-window & persistent p…
Browse files Browse the repository at this point in the history
…rivate browsing (brave/brave-ios#7575)
  • Loading branch information
Brandon-T authored Aug 4, 2023
1 parent 61d88c8 commit 4c2dc27
Show file tree
Hide file tree
Showing 104 changed files with 1,475 additions and 756 deletions.
4 changes: 4 additions & 0 deletions App/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
CA986FB4298C1254000C6DD8 /* BraveWidgetsModels in Frameworks */ = {isa = PBXBuildFile; productRef = CA986FB3298C1254000C6DD8 /* BraveWidgetsModels */; };
CAADEFE026E2707F0020DC4C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAADEFDF26E2707F0020DC4C /* SceneDelegate.swift */; };
CAAE653D287C9FCF00FA44A3 /* CPTemplateApplicationSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAAE653C287C9FCF00FA44A3 /* CPTemplateApplicationSceneDelegate.swift */; };
CABDE77F2A55DD1C00A388A4 /* AppState.swift in Sources */ = {isa = PBXBuildFile; fileRef = CABDE77E2A55DD1C00A388A4 /* AppState.swift */; };
E6231C011B90A44F005ABB0D /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = E6231C001B90A44F005ABB0D /* libz.tbd */; };
E6231C051B90A472005ABB0D /* libxml2.2.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = E6231C041B90A472005ABB0D /* libxml2.2.tbd */; };
F84B22041A0910F600AAB793 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F84B21E51A0910F600AAB793 /* AppDelegate.swift */; };
Expand Down Expand Up @@ -315,6 +316,7 @@
CA0391F7271E143F000EB13C /* SingleStatWidget.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SingleStatWidget.swift; sourceTree = "<group>"; };
CAADEFDF26E2707F0020DC4C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
CAAE653C287C9FCF00FA44A3 /* CPTemplateApplicationSceneDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CPTemplateApplicationSceneDelegate.swift; sourceTree = "<group>"; };
CABDE77E2A55DD1C00A388A4 /* AppState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppState.swift; sourceTree = "<group>"; };
E6231C001B90A44F005ABB0D /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
E6231C041B90A472005ABB0D /* libxml2.2.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libxml2.2.tbd; path = usr/lib/libxml2.2.tbd; sourceTree = SDKROOT; };
E62AC15F1E956AFC00843532 /* Dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Dev.entitlements; sourceTree = "<group>"; };
Expand Down Expand Up @@ -606,6 +608,7 @@
children = (
CAAE653C287C9FCF00FA44A3 /* CPTemplateApplicationSceneDelegate.swift */,
F84B21E51A0910F600AAB793 /* AppDelegate.swift */,
CABDE77E2A55DD1C00A388A4 /* AppState.swift */,
CAADEFDF26E2707F0020DC4C /* SceneDelegate.swift */,
);
path = Delegates;
Expand Down Expand Up @@ -1040,6 +1043,7 @@
2F4A572429E608C8003454F8 /* BrowserIntents.intentdefinition in Sources */,
F84B22041A0910F600AAB793 /* AppDelegate.swift in Sources */,
27BEDCCC28AD37CE0073425E /* BraveWidgets.intentdefinition in Sources */,
CABDE77F2A55DD1C00A388A4 /* AppState.swift in Sources */,
CAADEFE026E2707F0020DC4C /* SceneDelegate.swift in Sources */,
CAAE653D287C9FCF00FA44A3 /* CPTemplateApplicationSceneDelegate.swift in Sources */,
);
Expand Down
237 changes: 41 additions & 196 deletions App/iOS/Delegates/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,56 +32,23 @@ import Preferences
import BraveShields
import PrivateCDN

extension AppDelegate {
// A model that is passed used in every scene
struct SceneInfoModel {
let profile: Profile
let diskImageStore: DiskImageStore?
let migration: Migration?
let rewards: Brave.BraveRewards
}
}

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
private let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "app-delegate")

var window: UIWindow?
lazy var braveCore: BraveCoreMain = {
var switches: [BraveCoreSwitch] = []
if !AppConstants.buildChannel.isPublic {
// Check prefs for additional switches
let activeSwitches = Set(Preferences.BraveCore.activeSwitches.value)
let switchValues = Preferences.BraveCore.switchValues.value
for activeSwitch in activeSwitches {
let key = BraveCoreSwitchKey(rawValue: activeSwitch)
if key.isValueless {
switches.append(.init(key: key))
} else if let value = switchValues[activeSwitch], !value.isEmpty {
switches.append(.init(key: key, value: value))
}
}
}
switches.append(.init(key: .rewardsFlags, value: BraveRewards.Configuration.current().flags))
return BraveCoreMain(userAgent: UserAgent.mobile, additionalSwitches: switches)
}()

var migration: Migration?

private weak var application: UIApplication?
var launchOptions: [AnyHashable: Any]?

let appVersion = Bundle.main.infoDictionaryString(forKey: "CFBundleShortVersionString")

var receivedURLs: [URL]?

/// Object used to handle server pings
private(set) lazy var dau = DAU(braveCoreStats: braveCore.braveStats)

private var cancellables: Set<AnyCancellable> = []
private var sceneInfo: SceneInfoModel?

override init() {
private var cancellables: Set<AnyCancellable> = []

@discardableResult
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Hold references to willFinishLaunching parameters for delayed app launch
self.application = application

// Application Constants must be initialized first
#if MOZ_CHANNEL_RELEASE
AppConstants.buildChannel = .release
#elseif MOZ_CHANNEL_BETA
Expand All @@ -93,62 +60,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
#elseif MOZ_CHANNEL_DEBUG
AppConstants.buildChannel = .debug
#endif
super.init()
}

@discardableResult
func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Hold references to willFinishLaunching parameters for delayed app launch
self.application = application
self.launchOptions = launchOptions

// Brave Core Initialization
BraveCoreMain.setLogHandler { severity, file, line, messageStartIndex, message in
let message = String(message.dropFirst(messageStartIndex).dropLast())
.trimmingCharacters(in: .whitespacesAndNewlines)
if message.isEmpty {
// Nothing to print
return true
}

if severity == .fatal {
let filename = URL(fileURLWithPath: file).lastPathComponent
#if DEBUG
// Prints a special runtime warning instead of crashing.
os_log(
.fault,
dso: os_rw.dso,
log: os_rw.log(category: "BraveCore"),
"[%@:%ld] > %@", filename, line, message
)
return true
#else
fatalError("Fatal BraveCore Error at \(filename):\(line).\n\(message)")
#endif
}

let level: OSLogType = {
switch severity {
case .fatal: return .fault
case .error: return .error
// No `.warning` level exists for OSLogType. os_Log.warning is an alias for `.error`.
case .warning: return .error
case .info: return .info
default: return .debug
}
}()

let braveCoreLogger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "brave-core")
if AppConstants.buildChannel.isPublic {
braveCoreLogger.log(level: level, "\(message, privacy: .private)")
} else {
braveCoreLogger.log(level: level, "\(message, privacy: .public)")
}

return true
}

migration = Migration(braveCore: braveCore)

AppState.shared.state = .launching(options: launchOptions ?? [:], active: false)

// Passcode checking, must happen on immediate launch
if !DataController.shared.storeExists() {
Expand All @@ -157,14 +70,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// upon reinstall.
KeychainWrapper.sharedAppContainerKeychain.setAuthenticationInfo(nil)
}

return startApplication(application, withLaunchOptions: launchOptions)
}

@discardableResult
fileprivate func startApplication(_ application: UIApplication, withLaunchOptions launchOptions: [AnyHashable: Any]?) -> Bool {
log.info("startApplication begin")


// Set the Safari UA for browsing.
setUserAgent()

Expand All @@ -185,41 +91,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
SDImageCodersManager.shared.addCoder(PrivateCDNImageCoder())
SDImageCodersManager.shared.addCoder(SDImageSVGNativeCoder.shared)

// Setup Profile
let profile = BrowserProfile(localName: "profile")

// Setup DiskImageStore for Screenshots
let diskImageStore = { () -> DiskImageStore? in
do {
return try DiskImageStore(
files: profile.files,
namespace: "TabManagerScreenshots",
quality: UIConstants.screenshotQuality)
} catch {
log.error("Failed to create an image store for files: \(profile.files.rootPath) and namespace: \"TabManagerScreenshots\": \(error.localizedDescription)")
assertionFailure()
}
return nil
}()

// Setup ads
let rewardsConfiguration = BraveRewards.Configuration.current()
Migration.migrateAdsConfirmations(for: rewardsConfiguration)

// Setup Scene Info
sceneInfo = SceneInfoModel(
profile: profile,
diskImageStore: diskImageStore,
migration: migration,
rewards: BraveRewards(configuration: rewardsConfiguration))

// Perform migrations
let profilePrefix = profile.prefs.getBranchPrefix()
migration?.launchMigrations(keyPrefix: profilePrefix, profile: profile)

// Setup Custom WKWebView Scheme Handlers
setupCustomSchemeHandlers(profile)

// Temporary fix for Bug 1390871 - NSInvalidArgumentException: -[WKContentView menuHelperFindInPage]: unrecognized selector
if let clazz = NSClassFromString("WKCont" + "ent" + "View"), let swizzledMethod = class_getInstanceMethod(TabWebViewMenuHelper.self, #selector(TabWebViewMenuHelper.swizzledMenuHelperFindInPage)) {
class_addMethod(clazz, MenuHelper.selectorFindInPage, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
Expand All @@ -240,15 +111,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}

SystemUtils.onFirstRun()

// Schedule Brave Core Priority Tasks
braveCore.scheduleLowPriorityStartupTasks()

log.info("startApplication end")
return true
}

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppState.shared.state = .launching(options: launchOptions ?? [:], active: true)

// IAPs can trigger on the app as soon as it launches,
// for example when a previous transaction was not finished and is in pending state.
SKPaymentQueue.default().add(BraveVPN.iapObserver)
Expand Down Expand Up @@ -316,12 +184,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// There was a bug that when you skipped onboarding, default search engine preference
// was not set.
if Preferences.Search.defaultEngineName.value == nil {
sceneInfo?.profile.searchEngines.searchEngineSetup()
AppState.shared.profile.searchEngines.searchEngineSetup()
}

// Migration of Yahoo Search Engines
if !Preferences.Search.yahooEngineMigrationCompleted.value {
sceneInfo?.profile.searchEngines.migrateDefaultYahooSearchEngines()
AppState.shared.profile.searchEngines.migrateDefaultYahooSearchEngines()
}

if isFirstLaunch {
Expand Down Expand Up @@ -359,7 +227,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
if let weekOfInstall = Preferences.DAU.weekOfInstallation.value ??
Preferences.DAU.installationDate.value?.mondayOfCurrentWeekFormatted,
AppConstants.buildChannel != .debug {
braveCore.initializeP3AService(
AppState.shared.braveCore.initializeP3AService(
forChannel: AppConstants.buildChannel.serverChannelParam,
weekOfInstall: weekOfInstall
)
Expand All @@ -369,6 +237,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
await self.cleanUpLargeTemporaryDirectory()
}

Task(priority: .high) {
// Start preparing the ad-block services right away
// So it's ready a lot faster
await LaunchHelper.shared.prepareAdBlockServices(
adBlockService: AppState.shared.braveCore.adblockService
)
}

// Setup Playlist
// This restores the playlist incomplete downloads. So if a download was started
// and interrupted on application death, we restart it on next launch.
Task(priority: .low) { @MainActor in
PlaylistManager.shared.setupPlaylistFolder()
PlaylistManager.shared.restoreSession()
}

return shouldPerformAdditionalDelegateHandling
}

Expand All @@ -383,13 +267,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
#endif

func applicationWillTerminate(_ application: UIApplication) {
// We have only five seconds here, so let's hope this doesn't take too long.
sceneInfo?.profile.shutdown()

AppState.shared.profile.shutdown()

SKPaymentQueue.default().remove(BraveVPN.iapObserver)

// Clean up BraveCore
braveCore.syncAPI.removeAllObservers()
AppState.shared.braveCore.syncAPI.removeAllObservers()

log.debug("Cleanly Terminated the Application")
}
Expand All @@ -402,45 +285,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
}
}

func syncOnDidEnterBackground(application: UIApplication) {
// BRAVE TODO: Decide whether or not we want to use this for our own sync down the road

var taskId = UIBackgroundTaskIdentifier(rawValue: 0)
taskId = application.beginBackgroundTask {
print("Running out of background time, but we have a profile shutdown pending.")
self.shutdownProfileWhenNotActive(application)
application.endBackgroundTask(taskId)
}

sceneInfo?.profile.shutdown()
application.endBackgroundTask(taskId)
}

fileprivate func shutdownProfileWhenNotActive(_ application: UIApplication) {
// Only shutdown the profile if we are not in the foreground
guard application.applicationState != .active else {
return
}

sceneInfo?.profile.shutdown()
}

func setupCustomSchemeHandlers(_ profile: Profile) {
let responders: [(String, InternalSchemeResponse)] = [
(AboutHomeHandler.path, AboutHomeHandler()),
(AboutLicenseHandler.path, AboutLicenseHandler()),
(SessionRestoreHandler.path, SessionRestoreHandler()),
(ErrorPageHandler.path, ErrorPageHandler()),
(ReaderModeHandler.path, ReaderModeHandler(profile: profile)),
(IPFSSchemeHandler.path, IPFSSchemeHandler()),
(Web3DomainHandler.path, Web3DomainHandler())
]

responders.forEach { (path, responder) in
InternalSchemeHandler.responders[path] = responder
}
}

fileprivate func setUserAgent() {
let userAgent = UserAgent.userAgentForDesktopMode

Expand All @@ -455,10 +299,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// Record the user agent for use by search suggestion clients.
SearchViewController.userAgent = userAgent
}

func sceneInfo(for sceneSession: UISceneSession) -> SceneInfoModel? {
return sceneInfo
}

/// Dumps the temporary directory if the total size of the directory exceeds a size threshold (in bytes)
private nonisolated func cleanUpLargeTemporaryDirectory(thresholdInBytes: Int = 100_000_000) async {
Expand Down Expand Up @@ -494,7 +334,6 @@ extension AppDelegate: MFMailComposeViewControllerDelegate {
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
// Dismiss the view controller and start the app up
controller.dismiss(animated: true, completion: nil)
startApplication(application!, withLaunchOptions: self.launchOptions)
}
}

Expand All @@ -517,5 +356,11 @@ extension AppDelegate {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.

sceneSessions.forEach { session in
if let windowIdString = session.scene?.userActivity?.userInfo?["WindowID"] as? String, let windowId = UUID(uuidString: windowIdString) {
SessionWindow.delete(windowId: windowId)
}
}
}
}
Loading

0 comments on commit 4c2dc27

Please sign in to comment.