mirror of
https://github.com/stablyai/orca.git
synced 2026-11-15 02:17:31 +00:00
* 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
110 lines
3.6 KiB
Swift
110 lines
3.6 KiB
Swift
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
|
||
}
|
||
}
|