Files
orca/mobile/modules/orca-mobile-web-shell/tests/MobileWebShellChecks.swift
T

155 lines
6.6 KiB
Swift
Raw 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
// Everything the shell decides before WebKit is involved: the session id it will accept, the
// requests it will answer, the map it builds from a manifest, and the policy header. Compiled and
// run without a device:
//
// swiftc -O -o /tmp/mobile-web-shell-checks \
// ios/MobileWebShellOrigin.swift ios/MobileWebShellGeneration.swift ios/MobileWebShellCsp.swift \
// ios/MobileWebShellLoadState.swift ios/MobileWebShellResponseHeaders.swift \
// ios/MobileWebShellBridge.swift ios/MobileWebShellAppliedProps.swift \
// ios/MobileWebShellNavigationPolicy.swift \
// tests/MobileWebShellChecks.swift && /tmp/mobile-web-shell-checks
@main struct MobileWebShellChecks {
static func manifest(
schemaVersion: Int = 1,
entrypoint: String = "index.html",
assets: [[String: Any]] = [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": "assets/aa.js", "contentType": "text/javascript; charset=utf-8"],
["path": "assets/bb.png", "contentType": "image/png"]
]
) -> Data {
let root: [String: Any] = [
"schemaVersion": schemaVersion,
"entrypoint": entrypoint,
"assets": assets
]
return try! JSONSerialization.data(withJSONObject: root)
}
static func generation(_ data: Data) -> MobileWebShellGeneration? {
try? MobileWebShellGeneration.make(
manifestData: data,
directory: URL(fileURLWithPath: "/tmp/generation", isDirectory: true)
)
}
static func checkGenerationMap() {
guard let built = generation(manifest()) else { preconditionFailure("manifest rejected") }
// Only / may serve document bytes with CSP; no entrypoint alias.
precondition(built.entries["/index.html"] == nil)
precondition(generation(manifest(schemaVersion: 2)) == nil)
precondition(generation(manifest(entrypoint: "start.html")) == nil)
precondition(generation(manifest(assets: [])) == nil)
// The entrypoint must be one of the assets, or "/" would map to a file nobody declared.
precondition(generation(manifest(assets: [
["path": "assets/aa.js", "contentType": "text/javascript; charset=utf-8"]
])) == nil)
precondition(generation(manifest(assets: [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": "../escape.js", "contentType": "text/javascript; charset=utf-8"]
])) == nil)
precondition(generation(manifest(assets: [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": "assets/aa.js", "contentType": "text/javascript\r\nX-Injected: 1"]
])) == nil)
precondition(generation(manifest(assets: [
["path": "index.html", "contentType": "text/html; charset=utf-8"],
["path": 7, "contentType": "text/javascript; charset=utf-8"]
])) == nil)
let tooMany = (0..<257).map { index in
["path": "assets/a\(index).js", "contentType": "text/javascript; charset=utf-8"]
}
precondition(generation(manifest(assets: tooMany)) == nil)
// NSNumber bridges JSON true and 1.0 to Int; neither satisfies the integer-1 contract.
precondition(generation(Data(#"{"schemaVersion":true,"entrypoint":"index.html","assets":[{"path":"index.html","contentType":"text/html"}]}"#.utf8)) == nil)
precondition(generation(Data(#"{"schemaVersion":1.0,"entrypoint":"index.html","assets":[{"path":"index.html","contentType":"text/html"}]}"#.utf8)) == nil)
precondition(generation(Data(#"{"schemaVersion":"1","entrypoint":"index.html","assets":[{"path":"index.html","contentType":"text/html"}]}"#.utf8)) == nil)
precondition(generation(Data("not json".utf8)) == nil)
precondition(generation(Data("[]".utf8)) == nil)
}
static func checkLoadStateMachine() {
// Only the view’s own load raises the flag; commit, failure, renderer death and failed prop application clear it.
let ownLoad = MobileWebShellLoadStateMachine()
precondition(!ownLoad.isShellLoad)
ownLoad.shellLoadStarted()
precondition(ownLoad.isShellLoad)
ownLoad.committed()
precondition(!ownLoad.isShellLoad)
ownLoad.shellLoadStarted()
_ = ownLoad.failed(.documentLoadFailed)
precondition(!ownLoad.isShellLoad)
ownLoad.reset()
ownLoad.shellLoadStarted()
ownLoad.documentEnded()
precondition(!ownLoad.isShellLoad)
let progress = MobileWebShellLoadStateMachine()
precondition(progress.started()?.state == "loading")
precondition(progress.started() == nil)
progress.committed()
precondition(progress.finished()?.state == "ready")
precondition(progress.finished() == nil)
// replaceState rewrites the URL before finish; readiness must depend on commit state.
let unseated = MobileWebShellLoadStateMachine()
_ = unseated.started()
precondition(unseated.finished() == nil)
unseated.committed()
precondition(unseated.finished()?.state == "ready")
// A document replaced mid-load: the finish belongs to the one that is already gone.
let replaced = MobileWebShellLoadStateMachine()
replaced.committed()
replaced.documentEnded()
precondition(replaced.finished() == nil)
// A rule list compiles asynchronously, so it can fail after the generation was already refused.
let refused = MobileWebShellLoadStateMachine()
precondition(refused.failed(.generationUnreadable)?.reason == "generation-unreadable")
precondition(refused.failed(.isolationUnavailable) == nil)
precondition(refused.failed(.renderProcessGone) == nil)
precondition(refused.finished() == nil)
precondition(refused.started() == nil)
refused.reset()
precondition(refused.failed(.generationUnreadable)?.reason == "generation-unreadable")
// A document is heard only between its own commit and the end of that load.
let arming = MobileWebShellLoadStateMachine()
precondition(!arming.hasCommittedDocument)
_ = arming.started()
// The previous document is alive and same-origin until the next one commits.
precondition(!arming.hasCommittedDocument)
arming.committed()
precondition(arming.hasCommittedDocument)
// A new prop triple: the committed document is the one being replaced.
arming.reset()
precondition(!arming.hasCommittedDocument)
arming.committed()
arming.documentEnded()
precondition(!arming.hasCommittedDocument)
// A failure ends the document, and nothing after it re-arms: a retry is a remount.
arming.committed()
_ = arming.failed(.renderProcessGone)
precondition(!arming.hasCommittedDocument)
arming.committed()
precondition(!arming.hasCommittedDocument)
}
static func main() {
checkGenerationMap()
checkLoadStateMachine()
print("mobile web shell checks OK")
}
}