mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(mobile): open the OTA workspace page on iOS 18 (#29197)
WebKit reports no source frame for an app-initiated load() before iOS 18.5 (WebKit c28b242), so the iOS shell read its own document load as subframe-initiated and cancelled it, leaving the phone on "Opening workspace" forever. The navigation policy no longer takes the initiating frame as an input: the shell's own document loads when its one-shot load flag is up, the URL is the exact document URL, the target is the main frame, and it is not a download, matching the Android copy. Verified on an iOS 18.1 simulator (hang before, workspace opens after) and iOS 26.5 (unchanged). Refs #28626, STA-10545
This commit is contained in:
@@ -41,9 +41,9 @@ enum MobileWebShellNavigationPolicy {
|
||||
/// WKWebView, off-device: a sandboxed subframe navigating the top frame to the document URL
|
||||
/// arrives as `.other` with no gesture at all, and under a gesture-shaped rule that is an allow
|
||||
/// and a shell reload -- the bridge target cleared, the load state restarted, the page's state
|
||||
/// gone. `isFromSubframe` is the second discriminator for the same reason: the same probe shows
|
||||
/// the shell's own load arriving with source and target both the main frame, and a subframe's top
|
||||
/// navigation arriving with the subframe as its source.
|
||||
/// gone. The initiating frame is deliberately not an input: before iOS 18.5 WebKit reports no
|
||||
/// source frame for the view's own `load` (WebKit c28b242), and reading it refused the shell's
|
||||
/// own document. The one-shot flag already refuses a subframe's top navigation, as on Android.
|
||||
///
|
||||
/// What is left for the gesture is the only thing an artifact may ask for: a foreign URL, which
|
||||
/// is refused and handed to the opener. A download is not a document load, so it takes that path
|
||||
@@ -56,7 +56,6 @@ enum MobileWebShellNavigationPolicy {
|
||||
static func verdict(
|
||||
url: String?,
|
||||
isMainFrame: Bool,
|
||||
isFromSubframe: Bool,
|
||||
isDocumentUrl: Bool,
|
||||
isShellLoad: Bool,
|
||||
hasGesture: Bool,
|
||||
@@ -66,7 +65,7 @@ enum MobileWebShellNavigationPolicy {
|
||||
return .cancel
|
||||
}
|
||||
if isDocumentUrl {
|
||||
return isShellLoad && !isFromSubframe && !isDownload ? .allow : .cancel
|
||||
return isShellLoad && !isDownload ? .allow : .cancel
|
||||
}
|
||||
guard hasGesture, let offered = offerableUrl(url) else {
|
||||
return .cancel
|
||||
|
||||
@@ -479,7 +479,6 @@ final class OrcaMobileWebShellView: ExpoView, WKNavigationDelegate, WKUIDelegate
|
||||
let verdict = MobileWebShellNavigationPolicy.verdict(
|
||||
url: navigationAction.request.url?.absoluteString,
|
||||
isMainFrame: navigationAction.targetFrame?.isMainFrame == true,
|
||||
isFromSubframe: !navigationAction.sourceFrame.isMainFrame,
|
||||
isDocumentUrl: isDocumentUrl(navigationAction.request.url),
|
||||
isShellLoad: loadState.isShellLoad,
|
||||
hasGesture: navigationAction.navigationType == .linkActivated,
|
||||
|
||||
@@ -545,7 +545,6 @@ import Foundation
|
||||
func verdict(
|
||||
_ url: String? = "https://example.com/artifact-link",
|
||||
isMainFrame: Bool = true,
|
||||
isFromSubframe: Bool = false,
|
||||
isDocumentUrl: Bool = false,
|
||||
isShellLoad: Bool = false,
|
||||
hasGesture: Bool = true,
|
||||
@@ -554,7 +553,6 @@ import Foundation
|
||||
MobileWebShellNavigationPolicy.verdict(
|
||||
url: url,
|
||||
isMainFrame: isMainFrame,
|
||||
isFromSubframe: isFromSubframe,
|
||||
isDocumentUrl: isDocumentUrl,
|
||||
isShellLoad: isShellLoad,
|
||||
hasGesture: hasGesture,
|
||||
@@ -571,18 +569,14 @@ import Foundation
|
||||
// navigating the top frame to the document URL arrives with no gesture at all.
|
||||
precondition(verdict(document, isDocumentUrl: true, hasGesture: false) == .cancel)
|
||||
precondition(verdict(document, isDocumentUrl: true, hasGesture: true) == .cancel)
|
||||
precondition(
|
||||
verdict(document, isFromSubframe: true, isDocumentUrl: true, isShellLoad: true, hasGesture: false)
|
||||
== .cancel
|
||||
)
|
||||
// The second discriminator, on its own: a load the shell did not start is refused even when the
|
||||
// initiating frame is the main one, which is the page rewriting its own document away.
|
||||
// A load the shell did not start is refused whichever frame initiated it: the page rewriting
|
||||
// its own document away, or the sealed preview navigating the top frame.
|
||||
precondition(verdict(document, isDocumentUrl: true, isShellLoad: false, hasGesture: false) == .cancel)
|
||||
// A top-page meta refresh or a redirect to somewhere else: refused, and never opened.
|
||||
precondition(verdict(hasGesture: false) == .cancel)
|
||||
// A tap inside the sealed preview is exactly a subframe-initiated foreign navigation, and that
|
||||
// is the one thing the artifact is allowed to ask for.
|
||||
precondition(verdict(isFromSubframe: true) == .cancelAndOffer(foreign))
|
||||
precondition(verdict() == .cancelAndOffer(foreign))
|
||||
// A download is not a document load, so it is refused there rather than allowed; started by a
|
||||
// tap it reaches the opener, which is what makes `<a download>` behave as it does natively.
|
||||
precondition(
|
||||
@@ -591,11 +585,8 @@ import Foundation
|
||||
)
|
||||
precondition(verdict(isDownload: true) == .cancelAndOffer(foreign))
|
||||
// `<a href="/" download>`: a download that still names the shell's own document, which is the
|
||||
// one thing never handed to the opener. Refused from either frame, gesture or not.
|
||||
// one thing never handed to the opener. Refused, gesture or not.
|
||||
precondition(verdict(document, isDocumentUrl: true, isDownload: true) == .cancel)
|
||||
precondition(
|
||||
verdict(document, isFromSubframe: true, isDocumentUrl: true, isDownload: true) == .cancel
|
||||
)
|
||||
// A subframe is the sealed preview loading itself, which is not the user leaving the app.
|
||||
precondition(verdict(isMainFrame: false) == .cancel)
|
||||
precondition(verdict(isMainFrame: false, hasGesture: false) == .cancel)
|
||||
@@ -624,7 +615,6 @@ import Foundation
|
||||
MobileWebShellNavigationPolicy.verdict(
|
||||
url: document,
|
||||
isMainFrame: true,
|
||||
isFromSubframe: false,
|
||||
isDocumentUrl: true,
|
||||
isShellLoad: machine.isShellLoad,
|
||||
hasGesture: false,
|
||||
|
||||
Reference in New Issue
Block a user