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

Fix #8362: Fix Tabs Disappearing #8420

Merged
merged 2 commits into from
Nov 16, 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
7 changes: 6 additions & 1 deletion App/iOS/Delegates/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,12 @@ extension AppDelegate {
// 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) {
if let windowIdString = BrowserState.getWindowInfo(from: session).windowId,
let windowId = UUID(uuidString: windowIdString) {
SessionWindow.delete(windowId: windowId)
} else if let userActivity = session.scene?.userActivity,
let windowIdString = BrowserState.getWindowInfo(from: userActivity).windowId,
let windowId = UUID(uuidString: windowIdString) {
SessionWindow.delete(windowId: windowId)
}
}
Expand Down
1 change: 1 addition & 0 deletions App/iOS/Delegates/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ public class AppState {
DataController.shared.initializeOnce()
Migration.postCoreDataInitMigrations()
Migration.migrateTabStateToWebkitState(diskImageStore: diskImageStore)
Migration.migrateLostTabsActiveWindow()
}
break
case .active:
Expand Down
141 changes: 82 additions & 59 deletions App/iOS/Delegates/SceneDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import BraveCore
import BraveNews
import Preferences

private extension Logger {
static var module: Logger {
.init(subsystem: "\(Bundle.main.bundleIdentifier ?? "com.brave.ios")", category: "SceneDelegate")
}
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

// This property must be non-null because even though it's optional,
Expand All @@ -30,7 +36,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
static var shouldHandleInstallAttributionFetch = false

private var cancellables: Set<AnyCancellable> = []
private let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "scene-delegate")

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
Expand All @@ -48,8 +53,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

let conditions = scene.activationConditions
conditions.canActivateForTargetContentIdentifierPredicate = NSPredicate(value: true)
if let windowId = session.userInfo?["WindowID"] as? UUID {
let preferPredicate = NSPredicate(format: "self == %@", windowId.uuidString)
if let windowId = session.userInfo?["WindowID"] as? String {
let preferPredicate = NSPredicate(format: "self == %@", windowId)
conditions.prefersToActivateForTargetContentIdentifierPredicate = preferPredicate
}

Expand Down Expand Up @@ -156,7 +161,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
if let response = connectionOptions.notificationResponse {
if response.notification.request.identifier == BrowserViewController.defaultBrowserNotificationId {
guard let settingsUrl = URL(string: UIApplication.openSettingsURLString) else {
log.error("Failed to unwrap iOS settings URL")
Logger.module.error("[SCENE] - Failed to unwrap iOS settings URL")
return
}
UIApplication.shared.open(settingsUrl)
Expand All @@ -167,7 +172,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}

func sceneDidDisconnect(_ scene: UIScene) {
log.debug("SCENE DISCONNECTED")
Logger.module.debug("[SCENE] - Scene Disconnected")
}

func sceneDidBecomeActive(_ scene: UIScene) {
Expand Down Expand Up @@ -232,13 +237,13 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
guard let scene = scene as? UIWindowScene else {
log.debug("Invalid Scene - Scene is not a UIWindowScene")
Logger.module.error("[SCENE] - Scene is not a UIWindowScene")
return
}

URLContexts.forEach({
guard let routerpath = NavigationPath(url: $0.url, isPrivateBrowsing: scene.browserViewController?.privateBrowsingManager.isPrivateBrowsing == true) else {
log.debug("Invalid Navigation Path: \($0.url)")
Logger.module.error("[SCENE] - Invalid Navigation Path: \($0.url)")
return
}

Expand All @@ -247,7 +252,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
}

func scene(_ scene: UIScene, didUpdate userActivity: NSUserActivity) {
log.debug("Updated User Activity for Scene")
Logger.module.debug("[SCENE] - Updated User Activity for Scene")
}

func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
Expand Down Expand Up @@ -435,68 +440,86 @@ extension SceneDelegate {

if let userActivity = userActivity {
// Restore the scene with the WindowID from the User-Activity

let windowIdString = userActivity.userInfo?["WindowID"] as? String ?? ""
windowId = UUID(uuidString: windowIdString) ?? UUID()
isPrivate = userActivity.userInfo?["isPrivate"] as? Bool == true
urlToOpen = userActivity.userInfo?["OpenURL"] as? URL
let windowInfo = BrowserState.getWindowInfo(from: userActivity)
windowId = UUID(uuidString: windowInfo.windowId ?? "") ?? UUID()
isPrivate = windowInfo.isPrivate
urlToOpen = windowInfo.openURL
privateBrowsingManager.isPrivateBrowsing = isPrivate

// Create a new session window
SessionWindow.createWindow(isPrivate: false, isSelected: true, uuid: windowId)

scene.userActivity = BrowserState.userActivity(for: windowId, isPrivate: isPrivate)
scene.session.userInfo?["WindowID"] = windowId
scene.session.userInfo?["isPrivate"] = isPrivate
} else if let sceneWindowId = scene.session.userInfo?["WindowID"] as? String,
let sceneIsPrivate = scene.session.userInfo?["isPrivate"] as? Bool,
let windowUUID = UUID(uuidString: sceneWindowId) {
SessionWindow.createWindow(isPrivate: isPrivate, isSelected: true, uuid: windowId)

// Restore the scene from the Session's User-Info WindowID
scene.userActivity = BrowserState.userActivity(for: windowId.uuidString, isPrivate: isPrivate)
BrowserState.setWindowInfo(for: scene.session, windowId: windowId.uuidString, isPrivate: isPrivate)

windowId = windowUUID
isPrivate = sceneIsPrivate
privateBrowsingManager.isPrivateBrowsing = sceneIsPrivate
urlToOpen = scene.session.userInfo?["OpenURL"] as? URL

scene.userActivity = BrowserState.userActivity(for: windowId, isPrivate: isPrivate)
Logger.module.info("[SCENE] - USER ACTIVITY RESTORED")
} else {
// Should NOT be possible to get here.
// However, if a controller is NOT active, and tapping the app-icon opens a New-Window
// Then we need to make sure not to restore that "New" Window
// So we iterate all the windows and if there is no active window, then we need to "Restore" one.
// If a window is already active, we need to create a new blank window.

if let activeWindowId = SessionWindow.getActiveWindow(context: DataController.swiftUIContext)?.windowId {
let activeSession = UIApplication.shared.openSessions
.compactMap({ $0.userInfo?["WindowID"] as? String })
.first(where: { $0 == activeWindowId.uuidString })
let windowInfo = BrowserState.getWindowInfo(from: scene.session)
if let sceneWindowId = windowInfo.windowId,
let windowUUID = UUID(uuidString: sceneWindowId) {

// Restore the scene from the Session's User-Info WindowID
windowId = windowUUID
isPrivate = windowInfo.isPrivate
privateBrowsingManager.isPrivateBrowsing = windowInfo.isPrivate
urlToOpen = windowInfo.openURL

if activeSession != nil {
// An existing window is already active on screen
// So create a new window
let newWindowId = UUID()
SessionWindow.createWindow(isPrivate: false, isSelected: true, uuid: newWindowId)
windowId = newWindowId
scene.userActivity = BrowserState.userActivity(for: windowId.uuidString, isPrivate: isPrivate)
BrowserState.setWindowInfo(for: scene.session, windowId: windowId.uuidString, isPrivate: isPrivate)

Logger.module.info("[SCENE] - SCENE SESSION RESTORED")
} else if UIApplication.shared.supportsMultipleScenes {
if let activeWindowId = SessionWindow.getActiveWindow(context: DataController.swiftUIContext)?.windowId {
let activeSession = UIApplication.shared.openSessions
.compactMap({ BrowserState.getWindowInfo(from: $0) })
.first(where: { $0.windowId == activeWindowId.uuidString })

if activeSession != nil {
// An existing window is already active on screen
// So create a new window
windowId = UUID()
SessionWindow.createWindow(isPrivate: false, isSelected: true, uuid: windowId)
Logger.module.info("[SCENE] - CREATED NEW WINDOW")
} else {
// Restore the active window since none is active on screen
windowId = activeWindowId
Logger.module.info("[SCENE] - RESTORING ACTIVE WINDOW ID")
}
} else {
// Should be impossible to get here. There must always be an active window.
// However, if for some reason there is none, then we should create one.
windowId = UUID()
SessionWindow.createWindow(isPrivate: false, isSelected: true, uuid: windowId)
Logger.module.info("[SCENE] - WE HIT THE IMPOSSIBLE! - CREATING A NEW WINDOW ON MULTI-SCENE DEVICE!")
}

isPrivate = false
privateBrowsingManager.isPrivateBrowsing = false
urlToOpen = nil

scene.userActivity = BrowserState.userActivity(for: windowId.uuidString, isPrivate: false)
BrowserState.setWindowInfo(for: scene.session, windowId: windowId.uuidString, isPrivate: false)
} else {
// iPhones don't have a userActivity or session user info
if let activeWindowId = SessionWindow.getActiveWindow(context: DataController.swiftUIContext)?.windowId {
// Restore the active window since none is active on screen
windowId = activeWindowId
Logger.module.info("[SCENE] - RESTORING ACTIVE WINDOW ID")
} else {
// Should be impossible to get here. There must always be an active window.
// However, if for some reason there is none, then we should create one.
windowId = UUID()
SessionWindow.createWindow(isPrivate: false, isSelected: true, uuid: windowId)
Logger.module.info("[SCENE] - WE HIT THE IMPOSSIBLE! - CREATING A NEW WINDOW!")
}
} else {
// Should be impossible to get here. There must always be an active window.
// However, if for some reason there is none, then we should create one.
let newWindowId = UUID()
SessionWindow.createWindow(isPrivate: false, isSelected: true, uuid: newWindowId)
windowId = newWindowId

isPrivate = false
privateBrowsingManager.isPrivateBrowsing = false
urlToOpen = nil

scene.userActivity = BrowserState.userActivity(for: windowId.uuidString, isPrivate: false)
BrowserState.setWindowInfo(for: scene.session, windowId: windowId.uuidString, isPrivate: false)
}

isPrivate = false
privateBrowsingManager.isPrivateBrowsing = false
urlToOpen = nil

scene.userActivity = BrowserState.userActivity(for: windowId, isPrivate: false)
scene.session.userInfo = ["WindowID": windowId.uuidString,
"isPrivate": false]
}

// Create a browser instance
Expand Down Expand Up @@ -528,7 +551,7 @@ extension SceneDelegate {
let tabId = UUID(uuidString: tabIdString) {

let currentTabScene = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).filter({
guard let sceneWindowId = $0.session.userInfo?["WindowID"] as? String else {
guard let sceneWindowId = BrowserState.getWindowInfo(from: $0.session).windowId else {
return false
}

Expand Down
24 changes: 10 additions & 14 deletions Sources/Brave/Frontend/Browser/BrowserViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1168,17 +1168,17 @@ public class BrowserViewController: UIViewController {

let isPrivateBrowsing = SessionWindow.from(windowId: windowId)?.isPrivate == true
var userActivity = view.window?.windowScene?.userActivity
if userActivity == nil {
userActivity = BrowserState.userActivity(for: windowId, isPrivate: isPrivateBrowsing)

if let userActivity = userActivity {
BrowserState.setWindowInfo(for: userActivity, windowId: windowId.uuidString, isPrivate: isPrivateBrowsing)
} else {
userActivity?.targetContentIdentifier = windowId.uuidString
userActivity?.addUserInfoEntries(from: ["WindowID": windowId.uuidString,
"isPrivate": isPrivateBrowsing])
userActivity = BrowserState.userActivity(for: windowId.uuidString, isPrivate: isPrivateBrowsing)
}

view.window?.windowScene?.userActivity = userActivity
view.window?.windowScene?.session.userInfo = ["WindowID": windowId.uuidString,
"isPrivate": isPrivateBrowsing]
if let scene = view.window?.windowScene {
scene.userActivity = userActivity
BrowserState.setWindowInfo(for: scene.session, windowId: windowId.uuidString, isPrivate: isPrivateBrowsing)
}

for session in UIApplication.shared.openSessions {
UIApplication.shared.requestSceneSessionRefresh(session)
Expand Down Expand Up @@ -1996,12 +1996,8 @@ public class BrowserViewController: UIViewController {
}

func openInNewWindow(url: URL?, isPrivate: Bool) {
let activity = BrowserState.userActivity(for: UUID(), isPrivate: isPrivate)

if let url = url {
activity.addUserInfoEntries(from: ["OpenURL": url])
}

let activity = BrowserState.userActivity(for: UUID().uuidString, isPrivate: isPrivate, openURL: url)

let options = UIScene.ActivationRequestOptions().then {
$0.requestingScene = view.window?.windowScene
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Brave/Frontend/Browser/NavigationRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public enum NavigationPath: Equatable {

public init?(url: URL, isPrivateBrowsing: Bool) {
let urlString = url.absoluteString
if url.scheme == "http" || url.scheme == "https" || url.isIPFSScheme {
if url.scheme?.lowercased() == "http" || url.scheme?.lowercased() == "https" || url.isIPFSScheme {
self = .url(webURL: url, isPrivate: isPrivateBrowsing)
return
}
Expand Down
48 changes: 48 additions & 0 deletions Sources/Brave/Migration/Migration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,49 @@ public class Migration {
Preferences.Migration.tabMigrationToInteractionStateCompleted.value = true
}
}

public static func migrateLostTabsActiveWindow() {
if UIApplication.shared.supportsMultipleScenes { return }
if Preferences.Migration.lostTabsWindowIDMigrationOne.value { return }

let sessionWindows = SessionWindow.all()
guard let activeWindow = sessionWindows.first(where: { $0.isSelected }) else {
return
}

let windowIds = UIApplication.shared.openSessions
.compactMap({ BrowserState.getWindowInfo(from: $0).windowId })
.filter({ $0 != activeWindow.windowId.uuidString })

let zombieTabs = sessionWindows
.filter({ windowIds.contains($0.windowId.uuidString) })
.compactMap({
$0.sessionTabs
})
.flatMap({ $0 })

if !zombieTabs.isEmpty {
let activeURLs = activeWindow.sessionTabs?.compactMap({ $0.url }) ?? []

// Restore private tabs if persistency is enabled
if Preferences.Privacy.persistentPrivateBrowsing.value {
zombieTabs.filter({ $0.isPrivate }).forEach {
if !activeURLs.contains($0.url) {
SessionTab.move(tab: $0.tabId, toWindow: activeWindow.windowId)
}
}
}

// Restore regular tabs
zombieTabs.filter({ !$0.isPrivate }).forEach {
if !activeURLs.contains($0.url) {
SessionTab.move(tab: $0.tabId, toWindow: activeWindow.windowId)
}
}
}

Preferences.Migration.lostTabsWindowIDMigrationOne.value = true
}

public static func postCoreDataInitMigrations() {
if Preferences.Migration.coreDataCompleted.value { return }
Expand Down Expand Up @@ -213,6 +256,11 @@ fileprivate extension Preferences {
static let adBlockAndTrackingProtectionShieldLevelCompleted = Option<Bool>(
key: "migration.ad-block-and-tracking-protection-shield-level-completed", default: false
)

static let lostTabsWindowIDMigrationOne = Option<Bool>(
key: "migration.lost-tabs-window-id-one",
default: !UIApplication.shared.supportsMultipleScenes
)
}

/// Migrate a given key from `Prefs` into a specific option
Expand Down
Loading