# eduwin WebView2 模块 API

> 头文件：通过 `eduwin.h` 统一引用（`@public_api` 已自动合并）

封装 Microsoft Edge WebView2，在 Eduwin 窗口中嵌入现代浏览器引擎。纯 C 实现，通过 COM lpVtbl + 手写 vtable 回调接入 WebView2 的三层 API（Loader flat 入口 / ICoreWebView2 接口调用 / 异步 CompletedHandler 回调）。

**两种 Runtime 模式：**

| 模式                  | Runtime 来源 | 包体   | 客户端要求     |
| --------------------- | ------------ | ------ | -------------- |
| **Evergreen**（默认） | 系统共享     | —      | 需预装 Runtime |
| **Fixed Version**     | 应用本地打包 | ~150MB | 零依赖         |

> **⚠️ Runtime 获取：** `eduwin_pub.zip` 不含 WebView2 Runtime（体积过大）。
>
> - **Evergreen（推荐开发/测试用）**：下载 [Evergreen Bootstrapper](https://developer.microsoft.com/microsoft-edge/webview2/)（约 2MB），双击安装，重启即可
> - **Fixed Version（推荐分发用）**：下载 [Fixed Version Runtime](https://developer.microsoft.com/microsoft-edge/webview2/)（x64，约 150MB），解压后目录传给 `webview_set_runtime_path()`
>
> 两种下载均在同一页面，选择 "Evergreen Bootstrapper" 或 "Fixed Version" → 架构选 x64。

## 初始化 / 清理

```c
void webview_set_runtime_path(const WCHAR *path);  /* 必须在 webview_init() 之前 */
int  webview_init(void);
void webview_cleanup(void);
```

`webview_set_runtime_path()` 设置 Runtime 路径。`NULL` = Evergreen 模式（默认），传路径 = Fixed Version 模式。

`webview_init()` 必须在 `init_app()` **之前**调用。返回值 `0` 成功，非零失败。

`webview_cleanup()` 在 `run_app()` 返回后调用，释放全局 COM 环境。

```c
/* Fixed Version：客户端零依赖 */
webview_set_runtime_path(L"../runtime/");
if (webview_init() != 0) {
    init_app();
    alert(L"错误", L"WebView2 Runtime 初始化失败");
    return 1;
}
/*
 * Evergreen（默认）：
 *   不调 webview_set_runtime_path，webview_init() 自动用系统 Runtime。
 *   Windows 11 已预装，Windows 10 需手动安装。
 */
init_app();
// ... 创建 WebView 窗口 ...
run_app();
webview_cleanup();
```

## 创建 WebView

```c
HWND create_webview(HWND parent, int x, int y, int w, int h);
```

在父窗口中创建 WebView2 子控件。参数与 `create_button` / `create_label` 一致：`(x, y)` 左上角坐标，`(w, h)` 宽高。

WebView 的 Environment 和 Controller 是**异步创建**的，函数返回后 WebView 尚未就绪——需配合 `on_webview_ready` 回调使用。

```c
HWND win = create_window(L"WebView Demo", 960, 640);
HWND wv = create_webview(win, 0, 0, 960, 600);
```

## 导航 & 内容

```c
void webview_navigate(HWND hwv, const WCHAR *url);
void webview_load_html(HWND hwv, const WCHAR *html);
```

`webview_navigate` 导航到指定 URL。**通常在 `on_webview_ready` 回调中调用**，确保 WebView 已就绪。

`webview_load_html` 加载 HTML 字符串（`<html>...</html>`）。

如果在 `ready` 之前调用，请求会自动排队，WebView 就绪后执行。

```c
static void on_ready(HWND hwv, void *data) {
    webview_navigate(hwv, L"https://www.bing.com");
    // 或：
    // webview_load_html(hwv, L"<html><body><h1>Hello</h1></body></html>");
}
```

## 就绪回调

```c
typedef void (*webview_ready_fn)(HWND hwv, void *data);
void on_webview_ready(HWND hwv, webview_ready_fn cb, void *data);
```

WebView2 异步初始化完成后触发。这是安全调用 `webview_navigate` / `webview_load_html` / `webview_post_message` 的时机。

## JS 双向通信

C 与 WebView 中页面可通过以下两种方式进行双向通信。

### 方式一：原始 postMessage

```c
typedef void (*webview_message_fn)(HWND hwv, const char *json, void *data);
void on_webview_message(HWND hwv, webview_message_fn cb, void *data);
void webview_post_message(HWND hwv, const char *json);
```

**C → JS：** `webview_post_message` 向 WebView 中页面发送 JSON 字符串。页面通过 `window.chrome.webview.addEventListener('message', ...)` 接收。

**JS → C：** 页面通过 `window.chrome.webview.postMessage(json)` 发送消息，C 端通过 `on_webview_message` 注册的回调接收。

```c
// C 端接收 JS 消息
static void on_msg(HWND hwv, const char *json, void *data) {
    // json 是从 JS 发来的字符串
    log_info(L"收到 JS 消息");
    // 回复消息给 JS
    webview_post_message(hwv, "{\"reply\": \"你好 JS\"}");
}

// 注册
on_webview_message(wv, on_msg, NULL);
```

页面端示例：

```js
// JS 端发送消息到 C
window.chrome.webview.postMessage(JSON.stringify({ type: "click", value: 42 }));

// JS 端接收 C 消息
window.chrome.webview.addEventListener("message", (e) => {
    console.log("C 发来:", e.data);
});
```

### 方式二：Bridge（推荐）

Bridge 在页面启动时自动注入 `window.eduwin` 对象，封装了原始 postMessage，提供更简洁的双向调用方式。

```c
void webview_bridge_init(HWND hwv);
```

启用 Bridge。调用后 WebView 中所有页面自动获得 `window.eduwin.on_message(cb)` 和 `window.eduwin.call(func, params, callback)` 方法。

```c
typedef void (*webview_bridge_fn)(HWND hwv, const char *func_name, const char *params_json, void *data);
void webview_bridge_register(const char *func_name, webview_bridge_fn cb, void *data);
```

注册一个 C 端 bridge 处理函数，供 JS 通过 `eduwin.call(func_name, params)` 调用。`params_json` 是参数字符串（JSON 格式），如 `{"name":"eduwin"}`。

```c
/* C 端注册 */
static void on_hello(HWND hwv, const char *func, const char *params, void *d) {
    webview_post_message(hwv, "{\"reply\":\"你好！\"}");
}
webview_bridge_register("hello", on_hello, NULL);
```

```js
/* JS 端调用 */
eduwin.call("hello", { name: "eduwin" }, function (r) {
    console.log(r.reply); // "你好！"
});
```

Bridge 的底层仍基于 postMessage，消息类型为 `{ type: "bridge_call", func, params, id }`。C 端自动路由到已注册的处理函数，未被识别为 bridge 的消息仍会传给 `on_webview_message` 回调。

## 导航控制

```c
void webview_back(HWND hwv);
void webview_forward(HWND hwv);
void webview_reload(HWND hwv);
```

浏览器的后退、前进、刷新操作。仅在 WebView 就绪后有效。

## 导航完成回调

```c
typedef void (*webview_nav_complete_fn)(HWND hwv, int success, int status_code, void *data);
void on_navigation_completed(HWND hwv, webview_nav_complete_fn cb, void *data);
```

注册导航完成回调。页面加载完成后触发，`success` 为 1 表示成功，0 表示失败。

```c
static void on_nav(HWND hwv, int success, int code, void *d) {
    set_text(g_status, success ? L"Done" : L"Failed");
}
on_navigation_completed(wv, on_nav, NULL);
```

## Virtual Host — 加载本地网页文件

将本地文件夹映射为虚拟域名，绕过 `file:///` 的限制，正常加载外部 HTML/CSS/JS。

```c
void webview_set_virtual_host(HWND hwv, const WCHAR *host_name, const WCHAR *folder_path);
```

`host_name` 为虚拟域名（如 `L"eduwin.local"`），`folder_path` 为本地文件夹路径（相对路径基于 exe 所在目录，也支持绝对路径）。

```c
/* 映射当前 exe 目录到 eduwin.local */
webview_set_virtual_host(hwv, L"eduwin.local", L".");

/* 然后通过 https 访问本地文件 */
webview_navigate(hwv, L"https://eduwin.local/index.html");
```

> Virtual Host 的访问权限为 `COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS`，禁止跨域请求但允许正常加载同域资源。

## 完整示例

参见：

- `samples/webview_browser/` —— 简易浏览器（工具栏 + 地址栏 + 状态栏）
- `samples/webview_bridge/` —— Bridge 演示（C ↔ JS 双向通信 + 外部 HTML/CSS/JS 加载）

## 链接库

使用 WebView2 模块时，`build.bat` 需额外链接：

```bat
link ... WebView2LoaderStatic.lib ole32.lib oleaut32.lib version.lib advapi32.lib
```

| 库                         | 用途                            |
| -------------------------- | ------------------------------- |
| `WebView2LoaderStatic.lib` | WebView2 环境加载器             |
| `ole32.lib`                | COM 基础（CoInitializeEx 等）   |
| `oleaut32.lib`             | COM 自动化                      |
| `version.lib`              | 版本信息查询                    |
| `advapi32.lib`             | 注册表（检测 WebView2 Runtime） |

## 实现架构

```
eduwin_webview.c (纯 C)
├── 模式 A: Flat API ── CreateCoreWebView2EnvironmentWithOptions(...)
├── 模式 B: lpVtbl ──── controller->lpVtbl->put_Bounds(...)
│                       webview->lpVtbl->Navigate(...)
└── 模式 C: 回调 vtable ─ env_handler_t / ctrl_handler_t / msg_handler_t
                         (手写 QueryInterface / AddRef / Release / Invoke)
```

三种 COM 互操作模式全部在纯 C 中完成，未引入 `.cpp` 文件。
