Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Fix #7574: Add support for multi-window & persistent private browsing #7575

Merged
merged 13 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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