TypePHP\Qt
Guide
Widgets
Advanced
Reference
FAQ
  • English
  • 简体中文
GitHub
Guide
Widgets
Advanced
Reference
FAQ
  • English
  • 简体中文
GitHub
  • Widget Catalog

    • Widget Catalog
    • Containers
    • Input Controls
    • Data Controls
    • Display Controls
    • WebView

WebView

WidgetTree::webView() embeds a web view in your window. There are three backends, chosen at compile time — your PHP code is identical either way. Where capabilities differ, the table says so:

BackendWhereJSRemote http(s)://data:Local filezoom
WebView2Windows, when the SDK is enabled✅✅✅✅✅
WKWebViewmacOS (enabled by default)✅✅✅✅✅
QTextBrowserLinux, and when both switches above are off❌❌❌✅❌ silently ignored

QTextBrowser is Qt's built-in subset HTML renderer with no network stack at all: hand it https://… and you get an empty box, without an error. Apps that need a remote page or JS should degrade via webViewSupportsJs() rather than rely on it.

Ask which one you got:

$app->webViewBackend();      // 'webview2' | 'wkwebview' | 'textbrowser'
$app->webViewSupportsJs();   // bool

Use it to degrade gracefully rather than showing a blank box:

$app->view(function () use ($state, $app): array {
    return WidgetTree::vbox([
        $app->webViewSupportsJs()
            ? WidgetTree::webView($state->url, ['id' => 'wv', 'grow' => 1])
            : WidgetTree::label('This page needs JavaScript; the current backend cannot run it.',
                                ['id' => 'fallback']),
    ]);
});

Two ways to feed it

// 1. a URL — remote, or a local file resolved against the executable's directory
WidgetTree::webView('https://example.com', ['id' => 'wv']);
WidgetTree::webView('assets/help.html', ['id' => 'wv']);   // local file

// 2. an HTML string, rendered directly
WidgetTree::html('<h1>Hello</h1><p>Rendered inline.</p>', ['id' => 'wv']);
PropertyMeaning
urlAddress to load. Anything with a scheme (http://, https://, file://, data:) is passed to the backend as-is — but only WebView2 / WKWebView understand those schemes; QTextBrowser accepts file: and nothing else. Without a scheme it is treated as a local path resolved against the executable's directory (then the working directory), which all three backends support
htmlHTML string to render directly. Takes precedence over url when both are set
zoomZoom factor (WebView2 / WKWebView; QTextBrowser has no zoom concept and ignores it silently)

Events

EventWhenvaluepayload
loadednavigation finishedthe final URLsuccess (bool)
titledocument title changedthe title—
navigatingnavigation startedthe target URL—
linka new-window request (target="_blank")the URL—

link does not open anything by itself — it reports, and you decide. That is the same state-driven convention as the link widget:

$app->on('wv', 'link', function (array $event) use ($app) {
    $app->setStatus(['Blocked popup: ' . $event['value']]);
});

Navigation actions

Three imperative actions, for building a browser-style toolbar. They are not properties — there is no "currently reloading" state to write — so they are one-shot calls:

$app->webViewReload('wv');      // reload the current page
$app->webViewGoBack('wv');      // go back (no-op when there is no history)
$app->webViewGoForward('wv');   // go forward (no-op when there is no history)

They are driven through the same call channel as the table's appendRows / clear, so they take effect immediately rather than waiting for the next render.

Backendreload / goBack / goForward
WebView2✅
WKWebView (macOS)✅
QTextBrowser❌ silently ignored — it has no navigation stack

The no-op is deliberate, matching the "unknown property does not raise" convention: you do not need to branch on webViewBackend() before calling them.

Enabling WebView2

It is on by default in projects created by qtphp new on Windows. The switch is two lines in project.yml:

link-paths:
  - ../../third_party/webview2/x64      # the vendored SDK
link-libs:
  - WebView2Loader.dll.lib
cxx-flags:
  - /DQT_WEBVIEW2
  - /I"../../third_party/webview2/include"

Comment out /DQT_WEBVIEW2 and it falls back to QTextBrowser — the app still compiles and runs, just without JS.

qtphp build deploys the 195 KB WebView2Loader.dll next to your executable automatically. The runtime is not bundled: WebView2 apps need the WebView2 Runtime installed on the target machine, which Windows 10/11 ship with Edge. If it is missing, the widget shows an explanatory message instead of a blank box.

Enabling WKWebView (macOS)

project.macos.yml generated by qtphp new has it on by default. The switch is two lines in cxx-flags:

cxx-flags:
  - -DQT_WEBVIEW_WK     # use the system WebKit
  - -fobjc-arc          # WKWebView* members live in a C++ class, so ARC is required
ld-flags:
  - -Wl,-framework,WebKit
  - -Wl,-framework,Foundation
  - -Wl,-framework,AppKit

Comment out -DQT_WEBVIEW_WK and it falls back to QTextBrowser — the app still compiles and runs, just without JS.

This route needs no install: WebKit is a system framework, and the binary does not grow. We deliberately did not use Homebrew's qtwebview module — on macOS it drives that same system web view anyway (official docs: "On macOS, the system web view is used in the same manner as iOS"), while its packaging drags QtWebEngine (gigabytes) along with it.

The cost is that cpp-src/qt_webview_wk.mm must appear in the macOS sources. tpc's config merge replaces lists rather than appending to them, so the macOS entry rewrites the whole source list before adding that one line — which also means the Windows / Linux entries, which never list it, can never end up compiling Objective-C++.

Caveats

--shot cannot capture the native backends

WebView2 paints into its own child window and WKWebView is grafted onto the widget's native NSView — both sit outside Qt's painting system, so QWidget::grab() (which --shot uses) excludes that area and the PNG comes out blank there. This is a limitation of the backend, not a bug.

The QTextBrowser backend is drawn by Qt and therefore is captured.

To verify a native backend actually loaded and ran a page, use events instead of pixels: have the page's script set document.title and handle the title event — that title only appears if JS really executed (loaded works the same way).

--selftest and --difftest are unaffected: they dispatch events and never look at pixels.

Give it a height

A web view contributes nothing to Qt's layout size calculation (the native backend's pixels live outside the Qt widget tree). The widget ships a sensible default and a minimum height, but if you see it collapsed to a thin strip, give it 'grow' => 1 inside a container that can expand — and make sure the window itself is tall enough for the rest of the content.

JavaScript is unavailable on QTextBrowser

Anything that renders client-side — SPAs, most modern sites — will come out empty. Check webViewSupportsJs() and show your own fallback. Static HTML, documentation pages and simple reports work fine on all three backends.

Related

  • Widget Catalog — the other controls
  • Properties — common properties (grow, size, …)
  • Packaging — what ships next to the binary
Edit this page on GitHub
Last updated: 10/6/26, 3:13 AM
Prev
Display Controls