///|
/// Async fetcher signature used by the CORS gate. The actual network
/// roundtrip is driven by the caller-supplied closure, which may
/// either dispatch a real fetch (in the live path) or return a canned
/// response (in wbtests).
type ResponseFetcher = async (String, @http.FetchOptions) -> @http.HttpResponse raise @http.HttpError
///|
/// Sync OPTIONS preflight fetcher. The PreflightCache invokes this on
/// cache miss / expiry, never on a fresh hit. The cache contract is
/// sync (see `@http_cors.PreflightCache::get_or_validate`), so the
/// live wiring captures a `now`-bounded sync surface: a real network
/// preflight runs as an `async` call that the closure awaits before
/// returning the synthetic response.
type PreflightFetcher = (@http_cors.PreflightRequest) -> @http.HttpResponse
///|
/// Run a subresource fetch through the CORS gate:
///
/// 1. `@http_cors.classify_request` → Allow / PreflightRequired / Blocked.
/// 2. If `Blocked`, raise `CorsBlocked` without touching the network.
/// 3. If `PreflightRequired`, ask `cache.get_or_validate` to either
/// reuse a fresh entry or invoke `preflight_fetcher` once. A
/// preflight validation failure raises `PreflightFailed`.
/// 4. Dispatch the actual request via `fetcher`.
/// 5. Run `@http_cors.validate_actual_response` on the response and
/// raise `CorsBlocked` on Err. Same-origin responses skip the check
/// (the validator returns Ok early in that case).
///
/// Tests drive this seam directly by passing closures that return
/// canned `HttpResponse` values — no real network I/O required.
async fn script_fetch_with_cors(
url~ : String,
options~ : @http.FetchOptions,
preflight_cache~ : @http_cors.PreflightCache,
now~ : () -> Double,
preflight_fetcher~ : PreflightFetcher,
fetcher~ : ResponseFetcher,
) -> @http.HttpResponse raise @http.HttpError {
let decision = @http_cors.classify_request(
url~,
origin=options.origin,
mode=options.mode,
http_method=options.http_method,
headers=options.headers,
)
match decision {
Blocked(reason) => raise @http.HttpError::CorsBlocked(reason)
PreflightRequired(preflight_req) =>
match
preflight_cache.get_or_validate(preflight_req, now, preflight_fetcher) {
Err(msg) => raise @http.HttpError::PreflightFailed(msg)
Ok(_) => ()
}
Allow => ()
}
let response = fetcher(url, options)
// NoCors responses are opaque per Fetch spec: the script tag can
// still execute the body, but the page can't read it. We skip
// validate_actual_response in that mode — the validator assumes a
// CORS-mode request and would otherwise block every cross-origin
//