From 980640d3ea8454e199e8bc55599356bed69c7a70 Mon Sep 17 00:00:00 2001 From: Sharif Date: Wed, 1 Apr 2026 23:26:06 +0600 Subject: [PATCH] docs: add comprehensive WebView widget documentation (beta) - Add docs/WebView.md with full API reference, examples, JS bridge API, platform notes, error handling, debug mode, and size hints - Update sidebar with WebView section flagged as Beta - Update architecture.md with WebView helper process model, IPC protocol, and comparison table vs Tcl/Tk widgets Co-Authored-By: Claude Opus 4.6 --- docs/WebView.md | 410 +++++++++++++++++++++++++++++++++++++++++++ docs/_sidebar.md | 3 + docs/architecture.md | 52 ++++++ 3 files changed, 465 insertions(+) create mode 100644 docs/WebView.md diff --git a/docs/WebView.md b/docs/WebView.md new file mode 100644 index 0000000..170707f --- /dev/null +++ b/docs/WebView.md @@ -0,0 +1,410 @@ +# WebView Widget Beta + +> **Status: Beta** — The WebView API is functional and tested across Linux, macOS, and Windows, but may change before the next stable release. + +The **WebView** widget opens a **native browser window** powered by the platform's built-in engine — WebKitGTK on Linux, WKWebView on macOS, and WebView2 on Windows. It lets you build desktop apps with **HTML/CSS/JS frontends and a PHP backend**, similar to [Tauri](https://tauri.app) but for PHP. + +Unlike other widgets, WebView does **not** extend `AbstractWidget`. It creates a separate native window and communicates with PHP through a JSON-over-stdio IPC bridge. + +--- + +### How It Works + +``` +┌──────────────┐ JSON/stdio ┌──────────────────┐ +│ PHP Process │ ◄──────────────► │ WebView Helper │ +│ (your code) │ IPC bridge │ (native binary) │ +└──────────────┘ └──────────────────┘ + │ + Platform Engine + ┌───────┼───────┐ + │ │ │ + WebKitGTK WKWebView WebView2 + (Linux) (macOS) (Windows) +``` + +- **Commands** (JS → PHP): JavaScript calls PHP functions via `invoke()`, PHP returns values asynchronously. +- **Events** (PHP → JS): PHP pushes data to the frontend via `emit()`, JavaScript listens with `onPhpEvent()`. + +The helper binary is **auto-downloaded** on first use — no manual build steps required. + +--- + +### Requirements + +- PHP 8.1+ with `ext-ffi` +- **Linux**: WebKitGTK (`sudo apt install libwebkit2gtk-4.1-dev`) +- **macOS**: No extra dependencies (WKWebView is built-in) +- **Windows**: No extra dependencies (WebView2 is pre-installed on Windows 10/11) + +--- + +### Constructor + +```php +new WebView(array $options = []) +``` + +| Option | Type | Default | Description | +|----------|----------|--------------|----------------------------------------| +| `title` | `string` | `'WebView'` | Window title | +| `width` | `int` | `800` | Window width in pixels | +| `height` | `int` | `600` | Window height in pixels | +| `url` | `string` | `null` | URL to navigate to on startup | +| `html` | `string` | `null` | Raw HTML content to display on startup | +| `debug` | `bool` | `false` | Enable DevTools / inspector | + +--- + +### Methods + +#### Content + +| Method | Description | +|-------------------------------|--------------------------------------| +| `navigate(string $url)` | Navigate to a URL | +| `setHtml(string $html)` | Set the page to raw HTML content | + +#### Window + +| Method | Description | +|--------------------------------------------------|----------------------------------------------------------------| +| `setTitle(string $title)` | Change the window title | +| `setSize(int $w, int $h, int $hint = 0)` | Resize the window. Hint: `0` = none, `1` = min, `2` = max, `3` = fixed | + +#### JavaScript + +| Method | Description | +|----------------------------|----------------------------------------------------| +| `evalJs(string $js)` | Execute JavaScript in the webview | +| `initJs(string $js)` | Inject JS that runs automatically before each page load | + +#### Commands (JS → PHP) + +| Method | Description | +|--------------------------------------------------------|------------------------------------------------------| +| `bind(string $name, callable $callback)` | Register a PHP function callable from JavaScript | +| `unbind(string $name)` | Remove a binding | +| `returnValue(string $id, int $status, string $result)` | Return a value to JS. Status `0` = success, non-zero = error | + +#### Events (PHP → JS) + +| Method | Description | +|--------------------------------------------|------------------------------------------| +| `emit(string $event, mixed $payload = null)` | Send an event to the JavaScript frontend | + +#### Lifecycle + +| Method | Description | +|-----------------------------------|------------------------------------------| +| `onReady(callable $callback)` | Called when the webview is ready | +| `onClose(callable $callback)` | Called when the webview is closed | +| `onError(callable $callback)` | Called on error (fallback: `error_log`) | +| `destroy()` | Close the webview and terminate helper | +| `isReady(): bool` | Check if the webview is ready | +| `isClosed(): bool` | Check if the webview has been closed | +| `getId(): string` | Get the unique instance ID | + +--- + +### JavaScript Bridge API + +These functions are automatically available inside the webview: + +```javascript +// Call a PHP-bound function (returns a Promise) +invoke('functionName', arg1, arg2, ...) + +// Listen for events from PHP +onPhpEvent('eventName', function(payload) { + console.log(payload); +}) +``` + +--- + +### Examples + +#### Minimal WebView + +```php + 'Hello WebView', + 'width' => 800, + 'height' => 600, +]); + +$wv->setHtml('

Hello from PHP!

'); + +$wv->onClose(fn() => $app->quit()); +$app->addWebView($wv); +$app->run(); +``` + +#### Load a URL + +```php +$wv = new WebView(['title' => 'Browser']); +$wv->navigate('https://example.com'); + +$app->addWebView($wv); +``` + +#### Commands: Calling PHP from JavaScript + +Bind PHP functions that JavaScript can call. Each bound function receives a request ID and JSON-encoded arguments. + +**PHP (backend):** +```php +$wv->bind('greet', function (string $reqId, string $args) use ($wv) { + $data = json_decode($args, true); + $name = $data[0] ?? 'World'; + + $wv->returnValue($reqId, 0, json_encode("Hello, {$name}!")); +}); +``` + +**JavaScript (frontend):** +```javascript +const message = await invoke('greet', 'Alice'); +console.log(message); // "Hello, Alice!" +``` + +> Always call `returnValue()` inside your bind callback. Status `0` resolves the JS Promise, non-zero rejects it. + +#### Events: Pushing Data from PHP to JavaScript + +**PHP (backend):** +```php +$wv->emit('userUpdated', ['name' => 'Alice', 'role' => 'admin']); +``` + +**JavaScript (frontend):** +```javascript +onPhpEvent('userUpdated', function (user) { + document.getElementById('name').textContent = user.name; +}); +``` + +#### Inject Startup JavaScript + +Use `initJs()` to inject JS that runs before every page load — useful for configuration or polyfills: + +```php +$wv->initJs(' + window.AppConfig = { version: "1.0.0", debug: true }; +'); +``` + +#### Full Example: Todo App + +This example shows all the key patterns — commands, events, lifecycle hooks, and HTML rendering: + +**PHP:** +```php + 'Todo App', + 'width' => 900, + 'height' => 700, + 'debug' => true, +]); + +$todos = []; + +// JS → PHP: Get all todos +$wv->bind('getTodos', function (string $reqId, string $args) use ($wv, &$todos) { + $wv->returnValue($reqId, 0, json_encode($todos)); +}); + +// JS → PHP: Add a todo +$wv->bind('addTodo', function (string $reqId, string $args) use ($wv, &$todos) { + $data = json_decode($args, true); + $text = trim($data[0] ?? ''); + if ($text !== '') { + $todos[] = ['id' => uniqid(), 'text' => $text, 'completed' => false]; + $wv->emit('todosUpdated', $todos); // PHP → JS + } + $wv->returnValue($reqId, 0, json_encode(true)); +}); + +// JS → PHP: Toggle completion +$wv->bind('toggleTodo', function (string $reqId, string $args) use ($wv, &$todos) { + $id = json_decode($args, true)[0] ?? ''; + foreach ($todos as &$todo) { + if ($todo['id'] === $id) { + $todo['completed'] = !$todo['completed']; + break; + } + } + $wv->emit('todosUpdated', $todos); + $wv->returnValue($reqId, 0, json_encode(true)); +}); + +$wv->setHtml(' +

Todos

+ + + + +'); + +$wv->onClose(fn() => $app->quit()); +$app->addWebView($wv); +$app->run(); +``` + +#### Running Alongside Tcl/Tk Widgets + +WebView windows coexist with native Tk widgets in the same event loop: + +```php +$app = new Application(); + +// Native Tk window +$win = new \PhpGui\Widget\Window(['title' => 'Control Panel']); +$btn = new \PhpGui\Widget\Button($win->getId(), [ + 'text' => 'Send to WebView', + 'command' => fn() => $wv->emit('message', 'Hello from Tk!'), +]); +$btn->pack(); + +// WebView window +$wv = new WebView(['title' => 'Web Frontend']); +$wv->setHtml('

Waiting...

+ +'); + +$app->addWebView($wv); +$app->run(); +``` + +--- + +### Size Hints + +The `setSize()` method accepts a hint parameter: + +| Value | Constant | Behavior | +|-------|----------|-------------------------------------------------| +| `0` | NONE | Default — user can resize freely | +| `1` | MIN | Sets minimum size | +| `2` | MAX | Sets maximum size | +| `3` | FIXED | Fixed size — user cannot resize | + +```php +$wv->setSize(1024, 768, 3); // Fixed size, not resizable +``` + +--- + +### Error Handling + +Exceptions thrown inside `bind()` callbacks are automatically caught and returned to JavaScript as rejected Promises: + +```php +$wv->bind('riskyOperation', function (string $reqId, string $args) use ($wv) { + // If this throws, JS gets a rejected Promise with the error message + $data = json_decode($args, true); + if (empty($data[0])) { + throw new \InvalidArgumentException('Missing required argument'); + } + $wv->returnValue($reqId, 0, json_encode('Success')); +}); +``` + +Register a global error handler for IPC-level errors: + +```php +$wv->onError(function (string $message) { + error_log("WebView error: {$message}"); +}); +``` + +--- + +### Debug Mode + +Pass `'debug' => true` to enable the browser's built-in developer tools: + +```php +$wv = new WebView(['debug' => true]); +``` + +- **Linux/macOS**: Right-click → Inspect Element +- **Windows**: F12 or right-click → Inspect + +--- + +### Platform Notes + +| Platform | Engine | Notes | +|----------|-------------|---------------------------------------------------------| +| Linux | WebKitGTK | Requires `libwebkit2gtk-4.1-dev`. Needs display (X11 or Wayland). | +| macOS | WKWebView | Built-in, no extra dependencies. | +| Windows | WebView2 | Pre-installed on Windows 10/11. Falls back to Edge. | + +### Helper Binary + +The WebView widget relies on a small native helper binary that hosts the browser engine. It is automatically downloaded from GitHub Releases on first use. + +If auto-download fails (offline, firewall), build from source: + +```bash +cd vendor/developersharif/php-gui/src/lib/webview_helper +bash build.sh +``` + +**Linux build dependencies:** +```bash +sudo apt install cmake libgtk-3-dev libwebkit2gtk-4.1-dev +``` + +**macOS:** +```bash +brew install cmake +``` + +**Windows:** Requires CMake and Visual Studio Build Tools. + +--- + +### Notes + +- WebView does **not** extend `AbstractWidget` — it is a separate native window, not a Tcl/Tk widget. +- Multiple WebView instances can run simultaneously, each in their own process. +- The event loop in `Application::run()` polls all registered WebViews automatically. +- Call `$app->addWebView($wv)` to register a WebView with the event loop. +- Closing a WebView window triggers the `onClose` callback and auto-removes it from the event loop. +- The helper binary is platform-specific and named `webview_helper_{os}_{arch}` (e.g., `webview_helper_linux_x86_64`). diff --git a/docs/_sidebar.md b/docs/_sidebar.md index b426e56..5947c51 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -19,6 +19,9 @@ - [Combobox](Combobox.md) - [Message](Message.md) +- **WebView** Beta + - [WebView Widget](WebView.md) + - **Examples** - [Full Example](example.md) diff --git a/docs/architecture.md b/docs/architecture.md index 953b2dd..7b4270e 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -72,6 +72,58 @@ The library bundles native Tcl/Tk binaries for all platforms, so no system insta | Windows | `tcl86t.dll`, `tk86t.dll` | | macOS | `libtcl9.0.dylib`, `libtk9.0.dylib` | +## WebView Architecture Beta + +The WebView widget uses a **helper process** model instead of FFI. A small native binary hosts the platform's browser engine and communicates with PHP over JSON-over-stdio IPC. + +``` +PHP Application + ↓ +WebView Widget (PHP) + ↓ JSON/stdio IPC +webview_helper binary (C++) + ↓ +Platform Browser Engine + ├── WebKitGTK (Linux) + ├── WKWebView (macOS) + └── WebView2 (Windows) +``` + +### Key differences from Tcl/Tk widgets + +| Aspect | Tcl/Tk Widgets | WebView | +|----------------|-----------------------------|---------------------------------| +| Rendering | Native Tk controls | HTML/CSS/JS in browser engine | +| Bridge | FFI (in-process) | Separate helper process + IPC | +| Base class | Extends `AbstractWidget` | Standalone (no inheritance) | +| Communication | Tcl commands | JSON messages over stdin/stdout | + +### IPC Protocol + +Messages are newline-delimited JSON objects with a `version` field: + +```json +{"version":1,"cmd":"navigate","url":"https://example.com"} +{"version":1,"event":"ready"} +{"version":1,"event":"command","name":"greet","id":"0","args":"[\"Alice\"]"} +``` + +- **Commands** (PHP → Helper): `navigate`, `set_html`, `set_title`, `set_size`, `eval`, `init`, `bind`, `unbind`, `return`, `emit`, `ping`, `destroy` +- **Events** (Helper → PHP): `ready`, `closed`, `command`, `error`, `pong` + +### Helper Binary + +The helper binary is platform-specific and auto-downloaded from GitHub Releases on first use: + +| Platform | Binary | +|---------------|-------------------------------------| +| Linux x86_64 | `webview_helper_linux_x86_64` | +| macOS ARM | `webview_helper_darwin_arm64` | +| macOS Intel | `webview_helper_darwin_x86_64` | +| Windows x64 | `webview_helper_windows_x86_64.exe` | + +If auto-download fails, build from source: `cd src/lib/webview_helper && bash build.sh` + ## Namespace & Autoloading The project uses PSR-4 autoloading: