WebFront C++23
FR ↗GitHub ↗

Volume II / CHAPTER 03

For understanding and contributing

What happens during a call

Let us follow a request from a page button to a C++ function, then return with its reply.

Notation conventions

Function names, types, commands, options and filenames appear in monospace, for example describe, wf test or main.cpp. Italics introduce a term at its definition. Code blocks give the fragments to enter; the preceding text states their location and prerequisites.

A call expecting a result
  1. JavaScript page
  2. Codec and WebSocket
  3. WebLink and routing
  4. C++ callback

The reply or error returns to the waiting request. The two directions have different result contracts.

Loading the page, then talking to it

To display its first button, the browser needs a document and its resources. HTTP suits this task: the browser requests a resource and the server replies.

Once the page is open, we also want to communicate with the C++ program. Reloading the document on every click would be awkward. The WebSocket connection retains a channel on which the page and program can send messages.

On the C++ side, coroutines await the network provider’s accept, receive and send operations. TCPStdnet connects them to stdnet. The display-engine choice remains elsewhere: a WKWebView window and a system browser talk to the same server.

A click becomes a message

Return to the button requesting the next generation. The page first waited for webFront.ready, so the connection had been acknowledged. It then obtains the named function with webFront.cppFunction(name) and calls it with arguments.

The browser cannot enter a C++ function directly. A codec therefore turns the name and arguments into protocol bytes. At the other end, C++ decodes the message and finds the registered callback.

We still need to return to the caller. A result fulfils its promise; an error rejects it. Even a C++ function returning void can fail. Its JavaScript call therefore also produces a Promise, which the page must await or handle on rejection.

When C++ calls the page

In the other direction, not every call needs a result. To ask a page to display new state, ui.jsFunction(name) may suffice: it sends the call and ignores its return value.

If the program needs that value, it uses ui.jsFunction<R>(name) and receives a std::future<R>. ui.jsFunctionSender<R> composes the wait with tasks and their local cancellation. These choices express what the caller intends to do after sending.

An outstanding request must remain identifiable until its reply, failure or stop. If a reply arrives after stopping, it must not complete the same request again. We shall encounter this small race in the sender tests.

Agreeing on the bytes

The C++ code in Messages.hpp and JavaScript in js/core/ must give exchanged bytes the same meaning. Scalars, strings, tuples, numeric arrays and bitsets each have a defined representation. Adding a JavaScript object to an example does not create a representation for that object.

Consider a ten-bit bitset: two bytes suffice, but six bits in the last byte carry no useful data. The decoder checks that they are zero. This prevents different messages being accepted where the representation should be unique. Sizes and array types deserve the same attention.

When changing a message, we shall therefore need both codecs and their bounds. A C++ decoding test can establish a precise property; a real-browser test checks that the other end still agrees.

Check your understanding

Can a binary-format change be validated solely by a C++ test?