Files
orca/mobile/modules/orca-mobile-web-shell/ios/MobileWebShellLoadState.swift
Kelvin AmoabaandJinwoo-H a44cb9ac3e perf(mobile): prewarm one page while Home is shown so opening a page skips the WebView start (#40018)
* perf(mobile): answer a page's ready that arrived before its bridge host

A page's ready can reach the shell before the screen has built its bridge
host (the host waits on the client and the host profile read). It used to
be dropped, so init waited for the page's next retry, up to 2 s later.
Keep the latest ready per session and answer it once the host exists.

* perf(mobile): keep one prewarmed page document while Home is shown

While native Home is on screen, boot one page document for the most
recently connected host with a cached build, parked invisibly in the
window. Opening a page from that build adopts it instead of building a
WebView, which removes the WebView start and script parse from the tap.

The spare is dropped when Home is left without a claim (2 s grace), on
background, memory pressure, its own failure and before the cache deletes
its files. It is never kept beside a shown page, and only phones with
more than 3 GiB of RAM (4 GB phones) get one. A parked page is told to
stop asking for init; the adopting view answers the ready it sent.

Builds on Jinwoo's spare pool and replay work in #32203 and #32204.

Co-authored-by: Jinwoo-H <73622457+Jinwoo-H@users.noreply.github.com>

* fix(mobile): unregister the shell's memory callbacks from the context that registered them

---------

Co-authored-by: Jinwoo-H <73622457+Jinwoo-H@users.noreply.github.com>
2026-11-09 02:14:05 +00:00

110 lines
3.6 KiB
Swift
Raw Permalink 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(set) 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
}
}