mirror of
https://github.com/stablyai/orca.git
synced 2026-11-13 17:37:40 +00:00
* 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>
87 lines
2.9 KiB
Swift
87 lines
2.9 KiB
Swift
import Foundation
|
||
|
||
/// Shared page/native bridge contract; framework-free for off-device checks.
|
||
enum MobileWebShellBridge {
|
||
/// Use the same injected global/handler name on Android and iOS.
|
||
static let handlerName = "orcaBridge"
|
||
|
||
/// Tells a page booted ahead of its screen to stop asking for init; the screen that adopts it answers.
|
||
static let parkedFrame = #"{"v":1,"type":"parked"}"#
|
||
|
||
/// Enforce the shared UTF-8 JSON byte ceiling natively, before parsing.
|
||
static let maxMessageByteCount = 640 * 1024
|
||
|
||
/// Simulator 2026-09-18: WebKit lowercases custom-scheme hosts; use the request predicate’s ASCII-only fold for mixed-case base64url ids.
|
||
static func accepts(_ source: MobileWebShellBridgeSource, sessionId: String) -> Bool {
|
||
guard
|
||
source.isOurWebView,
|
||
source.isMainFrame,
|
||
source.hasCommittedDocument,
|
||
source.originProtocol == MobileWebShellOrigin.scheme,
|
||
MobileWebShellOrigin.isValidSessionId(sessionId),
|
||
MobileWebShellOrigin.asciiLowercased(source.originHost)
|
||
== MobileWebShellOrigin.asciiLowercased(sessionId)
|
||
else { return false }
|
||
return true
|
||
}
|
||
|
||
/// WebKit has no reply proxy: post only to the last accepted frame while its document is committed. WKFrameInfo outlives its frame.
|
||
static func canPost(
|
||
toFrameOriginHost host: String?,
|
||
sessionId: String,
|
||
hasCommittedDocument: Bool
|
||
) -> Bool {
|
||
guard
|
||
hasCommittedDocument,
|
||
let host,
|
||
MobileWebShellOrigin.isValidSessionId(sessionId),
|
||
MobileWebShellOrigin.asciiLowercased(host)
|
||
== MobileWebShellOrigin.asciiLowercased(sessionId)
|
||
else { return false }
|
||
return true
|
||
}
|
||
|
||
static func acceptsByteCount(_ byteCount: Int) -> Bool {
|
||
byteCount <= maxMessageByteCount
|
||
}
|
||
}
|
||
|
||
/// Bind frame and reported host atomically; every document boundary clears them, requiring the new page to speak before a native post.
|
||
struct MobileWebShellBridgeTarget<Frame> {
|
||
private var armed: (frame: Frame, originHost: String)?
|
||
|
||
var frame: Frame? { armed?.frame }
|
||
var originHost: String? { armed?.originHost }
|
||
|
||
mutating func arm(frame: Frame, originHost: String) {
|
||
armed = (frame: frame, originHost: originHost)
|
||
}
|
||
|
||
mutating func clear() {
|
||
armed = nil
|
||
}
|
||
}
|
||
|
||
/// A script message reduced to what the predicate reads, so the predicate needs no WebKit type.
|
||
struct MobileWebShellBridgeSource {
|
||
var isOurWebView: Bool
|
||
var isMainFrame: Bool
|
||
/// Whether a document has committed under the props this message is being judged against.
|
||
var hasCommittedDocument: Bool
|
||
var originProtocol: String
|
||
var originHost: String
|
||
}
|
||
|
||
/// Silently count refusals for tests without revealing the cap to the page.
|
||
final class MobileWebShellBridgeGate {
|
||
private(set) var refusedCount = 0
|
||
|
||
func accepts(byteCount: Int) -> Bool {
|
||
guard MobileWebShellBridge.acceptsByteCount(byteCount) else {
|
||
refusedCount += 1
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
}
|