
# Web API

除 ECMAScript 本身外，Phasis还提供多层 WHATWG / W3C API 套件：

- **Web 平台套件** — 纯值类型，无 I/O：`URL`, `URLSearchParams`, `URLPattern`, `TextEncoder`, `TextDecoder`, `atob`, `btoa`, `structuredClone`, `performance`, `DOMException`。
- **Fetch 套件** — 来自 JavaScript 的真实 HTTP：`fetch`, `Request`, `Response`, `Headers`, `Body`, `AbortController`, `AbortSignal`, `Blob`, `File`, `FormData`, `EventTarget`, `Event`, 完整 WHATWG Streams（`ReadableStream`, `WritableStream`, `TransformStream`, 排队策略, BYOB），`CompressionStream` / `DecompressionStream`（通过 zlib 支持 gzip / deflate / deflate-raw；加载 `kjdev/php-ext-brotli` PECL 扩展后支持 brotli），`navigator`，以及 HTML 规范的 `self` 全局别名 + `reportError`。
- **Crypto** — `crypto.getRandomValues`、`crypto.randomUUID` 和完整 `SubtleCrypto`：摘要（SHA-1/256/384/512）、HMAC、AES-GCM/CBC/CTR、RSA-OAEP/PSS/PKCS1、ECDSA（P-256/384/521）、ECDH、HKDF、PBKDF2，以及 JWK 导入/导出。由 `ext-openssl` 提供支持。
- **WebSocket** — 构造函数、RFC 6455 帧编解码器、事件。默认传输使用 `stream_socket_client`；可通过 `Engine::setWebSocketTransport()` 替换，以用于 ReactPHP / Ratchet / 测试模拟对象。
- **XMLHttpRequest** — 基于 fetch 传输层，因此适用相同的策略钩子和可替换传输层。
- **事件循环** — `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `queueMicrotask`。此外，`AsyncContext`（Stage 3）可在微任务/计时器边界之间传递值。

默认启用所有套件。无需标志或设置 — 在全新的 `new Engine()` 上，`typeof fetch`会返回 `"function"`。

## 一致性

导入的[Web Platform Tests](https://github.com/web-platform-tests/wpt)测试集（`tests/Wpt/fixtures/`）涵盖 `AbortController` / `AbortSignal`、`atob` / `btoa`、`Blob` / `File`、`TextEncoder` / `TextDecoder`、`EventTarget` / `Event`、`FormData`、`Headers`、`performance.now`、完整 Streams Standard（`Readable`/`Writable`/`Transform` + BYOB + 排队）、`structuredClone`、`URL` / `URLSearchParams`、完整的 **`crypto` / `SubtleCrypto`** 接口（摘要、HMAC、AES-GCM/CBC/CTR、RSA-OAEP/PSS/PKCS1、ECDSA、ECDH、HKDF、PBKDF2 — 包括来自 `hkdf` 和 `pbkdf2` 的数千种 derive-bits 组合）、**`WebSocket`** 状态机（构造函数 URL 和子协议验证、关闭代码语义、字符串的 `send()` / `ArrayBuffer` / 类型化数组视图 / `Blob`、`binaryType`、`bufferedAmount`、连接时异步关闭、事件处理器可空性），以及 **`Fetch`** + **`XMLHttpRequest`** 的广泛测试 — `responseType` 状态机、中止/发送转换、CORS 安全列表标头、多部分 `FormData` 上传、`XMLHttpRequestUpload` + `ProgressEvent`、`Response`/`Headers` 初始化验证、静态 `Response.json` / `Response.error` 构造函数、重定向链上的 Authorization 剥离、`URL.createObjectURL` blob URL、请求体混入消耗（`text` / `blob` / `json` / `formData` / `arrayBuffer`）、使用 `structuredClone`-d 分流流的 `Response.clone`，以及同步 XHR 网络错误抛出。所有导入的子测试均通过。

如需进一步扩展，请将新的 `*.any.js` 放入 `tests/Wpt/fixtures/<category>/` — 运行器会自动检测。XHR/Fetch 测试套件由 `tests/Wpt/fetch-server.php` 驱动（一个单文件模拟器，用于模拟 WPT `resources/*.py` 处理程序）；WebSocket 测试套件使用运行器内的惰性传输层来模拟 open/echo/close，足以支持已导入的测试样本。亲自运行测试集：

```bash
bin/wpt                # everything
bin/wpt --category headers --verbose   # one area, per-subtest output
bin/wpt --json         # machine-readable
```

## fetch() 示例

```js
const res = await fetch("https://api.example.com/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "alice" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const user = await res.json();
```

开箱即用。默认传输层是 PHP `ext-curl`。请求体会通过 `ReadableStream` Phasis 提供的 WHATWG 流传输，因此 `for await (const chunk of res.body)` 也能正常工作。

## AbortController 示例

```js
const ctrl = new AbortController();
setTimeout(() => ctrl.abort(), 5000);
try {
  const res = await fetch(slowUrl, { signal: ctrl.signal });
} catch (e) {
  if (e.name === "AbortError") {
    // request was cancelled
  }
}
```

`AbortSignal.timeout(ms)` 也已实现（惰性截止时间 — 在 `aborted` 读取时检查，而不是在事件循环中途触发事件；适用于在传输过程中轮询该信号的 fetch 传输层）。

## 嵌入器控制（PHP 端）

`Phasis\Engine` 类上的三个钩子可控制 fetch 行为：

```php
$engine = new Phasis\Engine();

// 1. Swap the HTTP transport. The callable receives a request descriptor
//    and an optional AbortSignal; returns a response descriptor.
$engine->setFetchTransport(function (array $request, $signal) {
    // ... use Guzzle, Symfony HttpClient, your stub, etc.
    return [
        'status' => 200,
        'statusText' => 'OK',
        'headers' => [['content-type', 'application/json']],
        'body' => '{"data":"value"}',
    ];
});

// 2. Pre-flight policy hook — rewrite, deny, or allow.
$engine->setFetchPolicy(function ($request) {
    $url = $request->get('url')->value;
    if (!str_starts_with($url, 'https://')) {
        throw new \RuntimeException('HTTPS only');
    }
    return null;  // allow as-is; return modified Request to rewrite
});

// 3. Cookie jar. Opt-in. Accepts any object with get(url):string and
//    set(url, header):void methods. JsObject or PHP object both work.
$engine->setCookieJar($myJar);
```

如果未调用 `setFetchTransport()`，则会运行默认的 `CurlTransport`。Cookie jar **默认不启用** — 默认没有 Cookie jar，除非你提供，否则不会存储任何 Cookie。

## Streams 示例

```js
// Producer: ReadableStream that yields numbers 1..5
const rs = new ReadableStream({
  start(controller) {
    for (let i = 1; i <= 5; i++) controller.enqueue(i);
    controller.close();
  },
});

// Consumer: async iteration
for await (const value of rs) {
  console.log(value);  // 1, 2, 3, 4, 5
}

// Pipe through a transform
const ts = new TransformStream({
  transform(chunk, controller) { controller.enqueue(chunk * 2); },
});
const doubled = new ReadableStream({
  start(c) { [1, 2, 3].forEach(n => c.enqueue(n)); c.close(); }
}).pipeThrough(ts);
for await (const v of doubled) console.log(v);  // 2, 4, 6
```

完整 Streams 规范：`ReadableStream`, `WritableStream`, `TransformStream`, `ReadableStreamDefaultController`, `ReadableByteStreamController`, `ReadableStreamDefaultReader`, `ReadableStreamBYOBReader`, `ReadableStreamBYOBRequest`, `WritableStreamDefaultController`, `WritableStreamDefaultWriter`, `TransformStreamDefaultController`, `ByteLengthQueuingStrategy`, `CountQueuingStrategy`，以及 `pipeTo` / `pipeThrough` / `tee`。

## 事件循环

`setTimeout`、`setInterval`、`clearTimeout`、`clearInterval` 和 `queueMicrotask` 位于全局对象上。微任务（Promise 处理程序、`queueMicrotask`）会在每个宏任务之间执行完毕；计时器通过每个 realm 的队列按截止时间顺序触发。

```js
console.log('sync');
queueMicrotask(() => console.log('microtask'));
setTimeout(() => console.log('timer'), 0);
// → sync, microtask, timer

const id = setInterval(() => console.log('tick'), 1000);
// ...later
clearInterval(id);

await new Promise(r => setTimeout(r, 50));   // common "delay" pattern works
```

由 Phasis 的 `Engine::runEventLoop()` 驱动，它会在同步代码执行完毕后自动调用 `eval()` 和 `evalAsModule()`。循环默认采用**虚拟时间**——`setTimeout(cb, 5000)` 不会休眠 PHP；循环会将时钟推进到截止时间，使程序迅速完成。使用实时环境的嵌入方自行驱动 `tickEventLoop()`。宿主侧接口请参见 [事件循环：宿主接口API](https://phasis.inline0.dev/zh/docs/api#event-loop)。

## *未*发布的内容

这些仅限浏览器或运行时模型的 API 刻意不在范围内 — 完整说明请参阅[已知限制](https://phasis.inline0.dev/zh/docs/compatibility/limitations)：

- `window`、`document`、DOM 树
- `localStorage`、`sessionStorage`、`IndexedDB`
- `Worker`、`SharedWorker`、`MessageChannel`
- Service Worker、Push API、通知、BroadcastChannel

如有需要，大多数功能都可由嵌入器通过 `$engine->setGlobal()` 进行 polyfill。

## 扩充 WPT 测试集

`tests/Wpt/upstream/` 是 [web-platform-tests/wpt](https://github.com/web-platform-tests/wpt) 的稀疏检出版本，涵盖 `fetch/`、`streams/`、`FileAPI/`、`xhr/formdata/`、`dom/abort/`、`dom/events/`。如需扩展覆盖范围，请将任意 `*.any.js` 文件从 `upstream/<area>/` 复制到 `tests/Wpt/fixtures/<category>/`。运行器下次运行时会自动检测；失败会通过 `bin/wpt --verbose` 记录。
