跪拜 Guibai
← Back to the summary

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:

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:

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:

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:

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:

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:

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:

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:

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:


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:


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:

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:


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:

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:


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:

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.

Source Code