WKWebView's `window` Is Just a Global Object — Here's How H5-to-Native Messaging Actually Works
What exactly is H5's window in WKWebView? A clear explanation of the communication essence between iOS and H5
Recently, I've been organizing a WKWebView demo that implements a very typical bidirectional communication scenario:
- H5 clicks a button
- Calls Native
- Native assembles a JSON
- Sends the JSON back to H5
- Finally, H5 displays the JSON string on the page
After getting the code to run, I found a question that is very suitable for "literacy education" for client-side developers:
What exactly is the window that often appears in H5? Why can it call Native?
This article explains this from the perspective of an iOS client developer.
1. Conclusion first: window is the global object of web page JavaScript
If you are a client-side developer, you can first remember this sentence:
window = the global object of the current web page's JavaScript runtime.
You can understand it as:
- The "outermost object" in the JS world
- The global context of the current web page's runtime environment
- The unified entry point for many capabilities on a web page
For example, many common frontend capabilities are attached to window:
window.location
window.alert()
window.setTimeout()
window.localStorage
In the WKWebView environment, iOS also injects additional capabilities into H5 through WebKit, such as:
window.webkit.messageHandlers
This is the key entry point for H5 to call Native.
2. What exactly does the most common line of code for client-side developers mean?
In the WKWebView scenario, we often see H5 code like this:
window.webkit.messageHandlers.nativeBridge.postMessage({
action: "getNativeJSON",
from: "h5"
});
It's easy to be confused the first time you see it.
Actually, it can be broken down into 5 layers:
window- The global object of the current web page
webkit- A layer of object injected by WebKit into the current page
messageHandlers- The collection of message channels registered by Native for H5
nativeBridge- One specific channel among them, the name is determined by Native when registering
postMessage(...)- The calling method for H5 to send a message to Native
So the whole sentence translated into client-side language is:
"H5 sends a message to iOS through the nativeBridge channel provided by WebKit."
3. Why can H5 directly write window.webkit.messageHandlers?
Because this object is not created by H5 out of thin air, but is pre-registered by Native.
On the iOS side, we usually register it like this:
userContentController.add(
WeakScriptMessageHandler(delegate: self),
name: pageConfiguration.jsBridgeName
)
If pageConfiguration.jsBridgeName = "nativeBridge", then H5 can access in the JS environment:
window.webkit.messageHandlers.nativeBridge
That is to say:
- Native registered a handler named
nativeBridge - WebKit exposes this handler to the JS environment
- H5 can call Native through
postMessage
This is the essence of H5 -> Native communication.
4. window is not a "bridge", it's just the "global object"
There is a common misunderstanding here:
Many client-side developers directly understand window as "JSBridge".
This statement is not accurate enough.
A more accurate understanding should be:
windowis the web page's global objectwindow.webkit.messageHandlers.xxxis the bridge entry point exposed by Native to H5
That is to say:
window is the container, and the bridging capability is just some properties attached to this container.
Just like you write in Swift:
app.network.request()
You wouldn't say app equals the network layer itself.
Similarly:
window.webkit.messageHandlers.nativeBridge.postMessage(...)
It doesn't mean window equals the Native Bridge, but rather that the Bridge is hung under the window object tree.
5. From an iOS client perspective, what's the best analogy to understand?
If we use an analogy more familiar to client-side developers, it can be remembered like this:
1. window is similar to the root object of the JS world
Similar to having a global entry object:
app
Then looking down layer by layer:
app.webkit.messageHandlers.nativeBridge.postMessage(...)
It's just that in JS, this root object is called window.
2. messageHandlers is similar to a list of capabilities exposed by Native
You can think of it as a "function registry".
For example, Native registers:
nativeBridgeloginBridgerouterBridge
Then H5 can access:
window.webkit.messageHandlers.nativeBridge
window.webkit.messageHandlers.loginBridge
window.webkit.messageHandlers.routerBridge
3. postMessage is similar to an RPC call
H5 sends a message object through postMessage, and Native receives it and dispatches based on action:
{ action: "getNativeJSON", from: "h5" }
This is actually very similar to the "route dispatch" or "protocol dispatch" common in client-side development.
6. The essence of H5 calling Native is actually "sending a message"
In our demo, the code for H5 to request Native JSON is roughly like this:
function requestNativeJSON() {
window.webkit.messageHandlers.nativeBridge.postMessage({
action: "getNativeJSON",
from: "h5"
});
}
This is not "directly calling a Swift method".
What H5 does is essentially:
- Send a message to Native
- Include action and parameters in the message
- Native decides how to handle it after receiving
The iOS side receives it in WKScriptMessageHandler:
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard message.name == pageConfiguration.jsBridgeName else { return }
if let body = message.body as? [String: Any], let action = body["action"] as? String {
switch action {
case "getNativeJSON":
sendNativeJSONToH5()
default:
break
}
}
}
So essentially:
H5 -> Native is not "direct function calling", but "message communication + Native dispatch processing".
7. How does Native send the result back to H5?
This is the other direction:
Native -> H5
The most common way in iOS is:
webView.evaluateJavaScript(script)
For example, in this demo, Native executes a script like this:
let script = "window.renderNativeJSONFromNative(\(jsonObjectString));"
webView.evaluateJavaScript(script)
Its meaning is:
- Native actively executes a piece of JS
- Calls
window.renderNativeJSONFromNative(...)in the page - Passes JSON data as a parameter to H5
And H5 has defined this function in advance:
function renderNativeJSONFromNative(payload) {
var resultNode = document.getElementById('nativeJSONResult');
resultNode.textContent = JSON.stringify(payload, null, 2);
}
This forms a complete closed loop:
- H5 sends a message to Native
- Native receives and processes it
- Native then executes JS to callback H5
- H5 updates the page to display the result
8. So bidirectional communication can be remembered in two sentences
This is especially suitable for sharing in a team:
H5 calls Native
window.webkit.messageHandlers.xxx.postMessage(...)
Meaning:
H5 sends a message to Native through the message channel injected by WebKit.
Native callbacks to H5
webView.evaluateJavaScript("window.xxx(...)")
Meaning:
Native actively executes a JS method in the page, passing the result back to H5.
To condense it further:
postMessage: H5 calls NativeevaluateJavaScript: Native calls H5
9. Why do many JSBridge designs revolve around window?
Because window is the most natural global entry point for the web page runtime.
If frontend code wants to expose a global function, the simplest way is to attach it to window:
window.renderNativeJSONFromNative = function(payload) { ... }
And when Native wants H5 to use certain capabilities, it usually leverages WebKit to inject into this global environment.
So you will see many JSBridge solutions, no matter how the name changes, the underlying layer cannot avoid two things:
- On the H5 side, find the bridge entry point through
window - On the Native side, call methods on
windowby executing JS
window is not the bridge itself, but it is often the "landing point" for bridging capabilities.
10. The most common misunderstandings client-side developers have when understanding window
Misunderstanding 1: window is an object injected by iOS
No.
window is inherently the global object in the browser / WebView.
What iOS injects are certain properties under it, for example:
window.webkit.messageHandlers
Misunderstanding 2: H5 is directly calling Swift methods
Also no.
H5 just sends a message to Native through postMessage, and Native dispatches it itself after receiving.
Misunderstanding 3: Native callback to H5 can only pass strings
Not entirely correct.
Essentially, Native assembles a piece of JS code for WebView to execute. You can pass strings, or you can serialize JSON and splice it into the JS call.
However, in engineering practice, special attention should be paid to:
- Escaping
- Injection security
- Unified data format
- Large object transmission cost
11. From an engineering practice perspective, how can this mechanism be upgraded?
This demo is a "literacy minimum closed loop", but online businesses usually continue to upgrade to a standard protocol.
For example, from:
{ action: "getNativeJSON" }
Upgrade to:
{
module: "user",
method: "getProfile",
params: { userId: "10001" },
callbackId: "cb_123"
}
Then Native uniformly returns:
{
"callbackId": "cb_123",
"code": 0,
"message": "success",
"data": {
"id": "10001",
"name": "Oliver"
}
}
This can support:
- Multi-module dispatch
- Asynchronous callbacks
- Promise wrapping
- Error code system
- Unified tracking and logging
At this point, when you look back at window, you will be clearer:
It is just the entry point of the JS world, the real focus is how the bridge protocol is defined.
12. Finally, end with the easiest sentence to remember
If you are a client-side developer, just remember the following three sentences, and you basically won't be confused by window anymore:
windowis the global object of web page JSwindow.webkit.messageHandlers.xxx.postMessage(...)is H5 calling NativeevaluateJavaScript("window.xxx(...)")is Native calling back H5
Ending
The first time I saw window.webkit.messageHandlers.nativeBridge.postMessage(...), I also felt it looked like "black magic".
But breaking it down, the essence is not complicated:
windowis the JS global objectwebkit.messageHandlersis the message channel exposed by Native to H5postMessageis H5 sending a messageevaluateJavaScriptis Native calling back
Once these four layers of relationships are sorted out, the vast majority of bridge code in WKWebView will become much clearer to read.
If you are also recently organizing knowledge related to WKWebView, JSBridge, or hybrid stacks, I hope this literacy essay can help you avoid a few detours.