Repository navigation
Conversation
The bridge object is installed in every frame, so a handler could not tell a call made by the top-level page from one made by a frame it embeds. JsMessage.sender now carries the sending frame's origin and whether it is the main frame, read from WKScriptMessage.frameInfo and never from the page's JSON. It is null on the backends whose engine does not report a sender yet.
WebView2 raises a view's WebMessageReceived for the top-level document only. A frame's chrome.webview.postMessage goes to an event of that frame, and is dropped when nothing subscribes to it, which this backend never does. So every bridge message on Windows is the main frame's, and is now reported as such. Its origin is not reported: the event names the sending document's URL, and a URL does not settle an origin. JsMessageSender.origin becomes optional for it. e2e: B14 has a sandboxed frame call the bridge and tell its page, whose own calls then arrive alone, each as the main frame's.
WebKitGTK tells the host nothing about who posted a script message. The window.ipc shim now hands each message to a relay script that runs in every frame, in a script world of its own: page scripts share the DOM with it but none of its JavaScript objects, so they can neither reach the message handler registered in that world nor change what the relay reads there. The relay stamps each message with the origin of its frame's document and whether that frame is the top one. A page reaches only the relay of its own frame, through an event on its window, which a document of another origin cannot dispatch. WebKit runs the two scripts in no fixed order, so a message sent before the relay has started in a document waits in the shim for it. A message that does not come through the relay arrives without a sender: one posted to the "ipc" handler directly, or sent between a page's document.open(), which removes the relay's listener, and the document the page then writes. e2e: B12 and B13 now run on Linux. B15 has a sandboxed frame try each way it has to send as the page that embeds it. B16 calls the bridge on both sides of document.open(). B17 has the suite view's init script call the bridge at document start.
angelix
force-pushed
the
js-message-sender
branch
from
November 11, 2026 23:16
f6bf925 to
7dfaf05
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The bridge object is installed in every frame (
forMainFrameOnly:NOon macOS,INJECT_ALL_FRAMESon Linux), so any frame a page embeds can call the sameIJsMessageHandlers as the page itself. A handler has no way to tell the two apart:JsMessagecarries onlycallbackId,methodNameandparams, all of which the caller writes.That matters for any handler that acts on the caller's authority, such as one that shows a permission prompt or returns account data: a third-party frame (an ad, an embedded widget) can make the call and it looks like the page made it.
What
JsMessagegainssender: JsMessageSender?:"sender"field.originis serialized likewindow.origin:scheme://host[:port], or"null"for an opaque origin. It isnull(no string) where the engine does not report one.senderisnullwhere nothing is known about the sender, so existing handlers are unaffected and a handler that needs it can refuse anull.Desktop backends report it through a new
NativeWebView.drainIpcMessagesWithSender(), whose default keeps every sendernull. Android, iOS and Wasm are unchanged.JsMessageis a data class, so the added constructor parameter (defaulted) is source compatible but changes the constructor/copysignature for already-compiled callers.macOS
From
WKScriptMessage.frameInfo(securityOrigin,isMainFrame).Linux
WebKitGTK's
script-message-receivedhands over the value only, so there is nothing to read a sender from. Thewindow.ipcshim now passes each message to a relay script that runs in every frame in a script world of its own (webkit_user_script_new_for_world, handler registered withregister_script_message_handler_in_world). Page scripts share the DOM with it but none of its JavaScript objects, so they can neither call that handler nor change what the relay reads: it stamps each message withwindow.originandwindow.top === windowof its own frame. A page reaches only its own frame's relay, through an event on itswindow, which a document of another origin cannot dispatch. WebKit starts the scripts of the two worlds in no fixed order, so a message sent before the relay has started in a document waits in the shim for it.A message that does not come through the relay has no sender: one posted to
webkit.messageHandlers.ipcdirectly, or sent between a page'sdocument.open(), which removes the relay's listener, and the document the page then writes (WebKit runs the document-start scripts again there, and the sender is back).Windows
ICoreWebView2::WebMessageReceivedis documented for the top-level document, and a frame'schrome.webview.postMessagegoes toICoreWebView2Frame2::WebMessageReceived. I checked what happens when nothing subscribes to the frame's event, which is how this backend works: onwindows-latestthe messages ofsrcdoc, sandboxed,data:and nested frames never reached the view's handler. So every bridge message is the main frame's and is reported as such.The origin is left out.
get_Sourceis a URL: it readabout:blankfor adata:page and for adata:frame, andabout:srcdocforsrcdocframes whether sandboxed or not, and a URL cannot say that a document is sandboxed into an opaque origin.Verify
./gradlew :e2e-desktop:run:windows-latest): 72 passed, 0 failed, 4 skipped (B12, B13, B15 and B17 are for the engines that report an origin). The suite does not complete onmainon Windows (e2e-windows hangs in :e2e-desktop:run and times out after 45 min #54), so this ran with the two Windows commits of Fix e2e-windows (#54), a second WebView in a window (#55), and the CI workflow #71 applied to this branch. With the Windows change taken out, B14 fails there withsenders={main=null, main-again=null}.Cases:
https://suite.local, main frame (macOS, Linux).srcdocframe reports originnull, not main frame (macOS, Linux).top, the embedder'sipc, a message that names its own sender); only its own messages arrive, as the frame's (macOS, Linux). With the relay moved into the page's world, this case fails with the forged message reported ashttps://suite.local, main frame.document.open()arrive (desktop).https://suite.local, main frame (macOS, Linux). On Linux it is the call that has to wait for the relay../gradlew :webview-compose:jvmTestpasses. The Android, Wasm and iOS (simulator) targets compile; I did not run them.Replies
Separately from this PR: callbacks are delivered with
evaluateJavaScriptinto whatever document is loaded when the handler answers, and callback ids restart at 0 in every document, so a reply can land in a later document's callback.WKScriptMessageHandlerWithReply,ICoreWebView2Frame2::PostWebMessageAsJsonandregister_script_message_handler_with_reply(2.40) each bind a reply to the asking document. Happy to follow up if that is wanted.