I needed to show a formatted report inside a Qt application. The report had headings, tables, a few images and links to related objects. My first attempt used a QTextDocument, and it was perfectly respectable until the design acquired a stylesheet and enough layout requirements to qualify as a small web page.

Qt 4.4’s QtWebKit module looked like the easy answer. Put the HTML in a QWebView, connect the link signal and go home. It did work, but the interesting part begins when the page needs to interact with the application. At that point there are three different worlds involved: Qt widgets, WebKit’s page and frame objects, and JavaScript running inside a frame.

The useful layers

QWebView is the widget. It fits into a normal layout and provides the familiar actions such as loading, back, forward and reload. It owns a QWebPage, which holds page-wide behavior: navigation policy, actions, settings and the main frame. The QWebFrame represents a frame’s document and is where HTML, JavaScript and the rendered contents meet.

For a simple report I can call setHtml() on the frame or view, optionally supplying a base URL so relative images and stylesheets resolve properly. That last argument is easy to omit and produces the pleasing result of a beautifully laid-out report with no images. I know this because I conducted the experiment several times.

Loading is asynchronous. A call to load() starts work and returns. Signals report progress and completion. Code that immediately asks for the final document after load() is racing the network and parser, even when the URL points to a local file. This is the same event-loop discipline as the rest of Qt, merely attached to a much more complicated component.

Crossing into JavaScript

QtWebKit can expose a QObject to JavaScript with addToJavaScriptWindowObject(). Slots and properties then become available in the script environment. This is convenient for a report that needs to request an application action. A link can call a narrow bridge object rather than encoding commands into invented URLs and parsing them later.

The narrow part is important. Exposing the main window would give page script an enormous accidental API. I prefer a small object with slots such as showItem(int) or requestPrint(). The bridge validates its arguments and emits signals; application code performs the actual work. The web page gets capabilities, not the keys to the building.

The JavaScript window object can be cleared when the frame’s context changes, so the bridge may need to be added again when notified. Treating injection as a one-time constructor chore works for the first page and mysteriously fails after navigation. Web pages have lifecycles too, because apparently one kind was not enough.

Communication can go in the other direction with evaluateJavaScript(). It is useful for updating a small piece of page state, but assembling large scripts from C++ strings becomes unpleasant immediately. For anything substantial I keep the logic in a JavaScript resource and invoke a defined function with carefully encoded data.

An embedded page should not automatically handle every clicked URL. The application may want internal links to select objects, ordinary HTTP links to open in the user’s browser, and unknown schemes to do nothing. QWebPage provides navigation hooks where that decision belongs.

I initially connected linkClicked() and assumed the job was finished. It was not: navigation behavior depends on the page’s link delegation policy. Once configured, the signal allows the application to decide rather than letting every click replace the report. This is less magical and therefore better.

Content is another policy boundary. For reports generated entirely by the application, exposing a bridge is manageable because the application controls the HTML and script. Doing the same for arbitrary remote pages is a different security proposition. A page that can invoke native slots has left the ordinary browser sandbox through the door I opened for it.

A large tool for a specific job

QtWebKit brings a real browser engine, not merely a richer label. That means capable HTML and CSS rendering, JavaScript, network loading, history and all the state accompanying them. It also means more memory and startup cost than QTextDocument. If the requirement is a few paragraphs with bold text, a web engine is unnecessary furniture.

Printing and exporting deserve testing as well. What looks right in the viewport may paginate badly. External resources may still be loading when the user presses Print. Font choices differ from the surrounding widgets unless the page stylesheet makes them deliberate. Embedding the web does not make it cease being the web.

For complex, interactive documents that already fit the HTML model, I strongly prefer QtWebKit to inventing a private layout engine from labels and painters. It gives designers familiar tools and keeps document structure out of widget code. I qualify that preference heavily: the page should be controlled, the native bridge should be tiny, and navigation should be explicit.

My report ended up simpler than the QTextDocument version, not because QtWebKit itself is simple, but because its complexity matches the problem. The trick is to use the browser as a document component and resist the urge to turn every dialog into a website. We have enough browsers already.