Files
orca/mobile/modules/orca-mobile-web-shell/ios/MobileWebShellNavigationPolicy.swift
Kelvin Amoaba ff54751393 fix(mobile): render inline HTML cards and previews on iOS (#36986)
The iOS web shell cancelled every subframe navigation, including the sealed about:srcdoc frames that HTML previews and chat visual cards render in, so they stayed blank. Allow exactly about:srcdoc subframes, let only a main-frame load spend the shell-load allowance, and never fail the shell document over a subframe response. Chromium never asks the Android shell about about: subframes, so Android already renders them.
2026-11-01 10:26:19 +00:00

54 lines
2.2 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
/// Keep allow/refuse/opener routing in one decision shared across platforms.
enum MobileWebShellNavigationVerdict: Equatable {
/// The served document, or a sealed srcdoc frame inside it, loading itself.
case allow
/// Refused, and the host is told nothing. Every navigation was this before the preview existed.
case cancel
/// Refused, and the URL is handed to the host's opener, which decides what may open.
case cancelAndOffer(String)
}
/// Framework-free navigation policy, checked off-device and kept in sync with Kotlin.
enum MobileWebShellNavigationPolicy {
/// Cap native-to-JS URL traffic; TypeScript’s readBridgeExternalLinkUrl owns scheme filtering and its smaller 2048-character limit.
static let maxCancelledNavigationUrlCharacters = 4096
/// The URL a frame's srcdoc document loads under; it carries the embedder's CSP and the frame's sandbox.
static let inlineDocumentUrl = "about:srcdoc"
/// Allow only sealed srcdoc subframes (HTML previews and chat visuals); allow the document only with the one-shot shell-load flag, spent at decision.
/// Other document URLs (including downloads) are never offered to the opener; gestured foreign URLs may be.
/// Gestures cannot authorize loads: sandboxed top navigation can report .other without one.
/// Ignore sourceFrame: WebKit before iOS 18.5 omits it for our own load (c28b242).
/// TypeScript’s readBridgeExternalLinkUrl owns scheme filtering so OTA policy has one source.
static func verdict(
url: String?,
isMainFrame: Bool,
isDocumentUrl: Bool,
isShellLoad: Bool,
hasGesture: Bool,
isDownload: Bool
) -> MobileWebShellNavigationVerdict {
guard isMainFrame else {
return url == inlineDocumentUrl && !isDownload ? .allow : .cancel
}
if isDocumentUrl {
return isShellLoad && !isDownload ? .allow : .cancel
}
guard hasGesture, let offered = offerableUrl(url) else {
return .cancel
}
return .cancelAndOffer(offered)
}
/// The URL a cancelled navigation may be offered under, or nil when nothing crosses.
static func offerableUrl(_ url: String?) -> String? {
guard let url, !url.isEmpty, url.count <= maxCancelledNavigationUrlCharacters else {
return nil
}
return url
}
}