Files
orca/mobile/modules/orca-mobile-web-shell/ios/MobileWebShellLoadState.swift
T
Kelvin Amoaba 1363f16782 fix(mobile): keep the page after the phone reclaims its WebView in the background (#39415)
* fix(mobile): remount silently after background WebView losses

* fix(mobile): hold a reclaimed remount until the app is in front

* fix(mobile): give a reclaimed renderer one free remount per painted document

* fix(mobile): report a dead Android renderer again with its original loss facts

* refactor(mobile): build the page update-failure record in one place

* fix(mobile): declare desktop-bound native verbs in the verb table and let the device store skip the verdict hold

* refactor(mobile): name the update-failure record builder after the effect it returns

* test(mobile): check the device store read before the verdict actually succeeds

* test(mobile): leave the store-verb hold test to its own PR
2026-11-07 23:13:47 +00:00

110 lines
3.6 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Foundation
/// The wire names the TypeScript parser accepts; a swap here is a silent change of meaning.
enum MobileWebShellFailureReason: String {
case generationUnreadable = "generation-unreadable"
case isolationUnavailable = "isolation-unavailable"
case documentLoadFailed = "document-load-failed"
case renderProcessGone = "render-process-gone"
}
struct MobileWebShellLoadEmission: Equatable {
let state: String
let reason: String?
/// Renderer losses only: Orca was off screen, so the OS reclaimed the process rather than the page failing.
var background: Bool? = nil
var payload: [String: Any] {
var payload: [String: Any] = ["state": state]
if let reason {
payload["reason"] = reason
}
if let background {
payload["background"] = background
}
return payload
}
}
/// Failure is terminal and duplicate reports are dropped: late error-document/rule-list callbacks must never restore ready.
final class MobileWebShellLoadStateMachine {
private var isTerminal = false
private var last: MobileWebShellLoadEmission?
/// Accept messages only after the current props’ document commits; the replaced same-origin document can still speak before then.
private(set) var hasCommittedDocument = false
/// Keep the own-load flag in the state machine so every document-ending path clears it.
private(set) var isShellLoad = false
/// The view is about to load the document itself. The only thing that raises the flag.
func shellLoadStarted() {
isShellLoad = true
}
/// Spend the own-load flag at the policy decision: WebKit may decide a second action before the first commits.
func shellLoadConsumed() {
isShellLoad = false
}
/// A new prop pair. Nothing else reopens a terminal state: a retry is a remount.
func reset() {
isTerminal = false
last = nil
documentEnded()
}
func committed() {
isShellLoad = false
guard !isTerminal else { return }
hasCommittedDocument = true
}
/// The committed document is gone: a new load, a failure, or a renderer that died.
func documentEnded() {
hasCommittedDocument = false
isShellLoad = false
}
func started() -> MobileWebShellLoadEmission? {
emit(MobileWebShellLoadEmission(state: "loading", reason: nil))
}
/// Require a commit, not URL equality: history.replaceState changes / to /h/<hostId> before finish, otherwise ready never arrives.
func finished() -> MobileWebShellLoadEmission? {
guard hasCommittedDocument else { return nil }
return emit(MobileWebShellLoadEmission(state: "ready", reason: nil))
}
func failed(
_ reason: MobileWebShellFailureReason,
background: Bool? = nil
) -> MobileWebShellLoadEmission? {
let emission = emit(
MobileWebShellLoadEmission(state: "failed", reason: reason.rawValue, background: background)
)
isTerminal = true
documentEnded()
return emission
}
private func emit(_ emission: MobileWebShellLoadEmission) -> MobileWebShellLoadEmission? {
guard !isTerminal, emission != last else { return nil }
last = emission
return emission
}
}
/// Ignore stopLoading/policy cancellations so healthy generations are not redownloaded. Legacy WebKitErrorDomain frame-load codes lack iOS SDK symbols.
enum MobileWebShellNavigationError {
static let webKitDomain = "WebKitErrorDomain"
static let frameLoadInterruptedByPolicyChange = 102
static func isIgnorable(domain: String, code: Int) -> Bool {
if domain == NSURLErrorDomain, code == NSURLErrorCancelled {
return true
}
return domain == webKitDomain && code == frameLoadInterruptedByPolicyChange
}
}