Hello Blink API owners,
We want a discussion around the JavaScript API in our feature for Android WebView, although the implementation won’t touch Blink code, we want to get consensus from Blink side before we implement/ship it because of the new JavaScript API. Wrote the email in an Intent to Implement format so hopefully could answer basic questions in many degrees.
Contact emails
ct...@chromium.org, chan...@chromium.org, des...@chromium.org
Explaniner
Since this feature was intended for Android release, we didn’t want to expose this to the public. Now we intend to ship this feature with AndroidX (a.k.a the Android support library). We had internal first-party survey to discuss these APIs.
Design doc
go/js-java-interaction-design (Google internal doc)
Summary
We want to provide an easier way for developers to initialize the communication between JavaScript and Java, and allow developers to have better timing to inject JavaScript code. We want to provide a JavaScript API and its corresponding Java callback to initialize the communication from JavaScript side.
chrome.runtime.postMessage(message[, ports])
And a Java API to inject custom JavaScript to use the new JavaScript API and may override other JavaScript APIs.
WebViewCompat#setDocumentStartJavascript()
Motivation
In Android WebView, we have addJavascriptInterface(), evaluateJavascript() and postWebMessage() for developers to inject JavaScript and establish JavaScript and Java communication from Java side. There are several issues with the current APIs:
Security issue with addJavascriptInterface, the injected Java object could be called from any frame, there is no way to tell the origin of that call.
evaluateJavascript() and postWebMessage() are only designed for main frame.
There is no good event from WebView for developers to tell when is the right time to establish communication.
Risks
Interoperability and Compatibility
This feature is targeting Android WebView, so used only by Android app developers. We don’t think other major browsers will implement this feature since there is no such need for them.
Ergonomics
The JavaScript API sits in the center of communication to Android WebView, so it could be used with many other APIs, we are expecting developers use it to provide Android platform specific functionality from Java to JavaScript.
Activation
Developers should be able to use it by using AndroidX.
Will this feature be supported on all six Blink platforms (Windows, Mac, Linux, Chrome OS, Android, and Android WebView)?
Android WebView only.
Requesting approval to ship?
No.
And a Java API to inject custom JavaScript to use the new JavaScript API and may override other JavaScript APIs.
WebViewCompat#setDocumentStartJavascript()
--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CA%2BjkKu%3DaYWD74BndqqtuJpi-UZ7mthAQ%3DXSG0MHeQgBncJ%2BKww%40mail.gmail.com.
And a Java API to inject custom JavaScript to use the new JavaScript API and may override other JavaScript APIs.
WebViewCompat#setDocumentStartJavascript()How widely will this API be used?
Running JavaScript at the document start timing will have a risk of slowing down loading dramatically.(c.f., Content scripts that run at document_start slow down loading. Developers are encouraged to inject content scripts at document_idle.)
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CABg10jy_U2pgAp2CO6x892Wd7jmPQs6ix-J4%3DUuXt5hsKUYZMQ%40mail.gmail.com.
Unlike with extension, in WebView there are many other time points that app developer could inject JavaScript.
Unlike with extension, in WebView there are many other time points that app developer could inject JavaScript.Do you have a list of injection points for scripts in WebView?
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CABg10jxU94S3UvMjQN9JdL1RizGJidtZiOCR1Bx-%3Dgcr-pcfPA%40mail.gmail.com.
We had a brief offline chat around this topic today, and thought we could introduce setInjectedJavascript() with document_{start,idle,end} as an argument to replace setDocumentStartJavascript() to give the flexibility to app developers, also encourage them to use document_idle when possible. How does that sound?
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CAET19jRBgHL5QCO7ahHJzw%2BBDJ9ZA-74peH4-QVEg75f0DnEKg%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CA%2BjkKumMZczCM9t7S%2B8_7WSzVCWsvx0fH2ya-pDOsbZdQir4pA%40mail.gmail.com.
Thanks for flagging this, Shimi and for the plus in, Ojan.Discussed at today's API OWNERS and the addition of the new JS API is slightly concerning as it isn't clear that it's strictly necessary.From the design doc (which there should be a public version of), the addition of methods to chrome.runtime.* which mirror existing methods on window seems strange when it seems possible to use a specially named message source instead. That might allow you to use existing postMessage/onmessage infrastructure without any new public API.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CANr5HFW4ArhcC_8pSiSp-Nv-tSTzvYa%2BpSqzVC8RO5qfvS52vQ%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CANr5HFW4ArhcC_8pSiSp-Nv-tSTzvYa%2BpSqzVC8RO5qfvS52vQ%40mail.gmail.com.
WDYT, Chris/Torne?On Sun, Mar 10, 2019 at 3:48 PM Changwan Ryu <chan...@chromium.org> wrote:How about specifying the name for the postMessageObject, then?I think that's a good idea. To be clear, I think you meant to specify a name for the MessagePort.We still want to have a listener to form a N:1 communication, otherwise there is no good way to get the port from Java side. But the semantic will be changed:- onWebMessagePortInjected(WebMessagePort port, String sourceOrigin) instead of onWebMessage(message, sourceOrigin, ports);- setWebPortListener(webView, listener, "port_name", allowedUris) instead of the method setWebMessageListener()Though we need to figure out how to inject a port, but I believe that should not be too hard.setWebMessageListener(webView, listener, "_webview_post_message", allowedUris);And then from JavaScript_webview_post_message.postMessage(message, ports)This is probably natural to follow comparing with the below choice, since MessagePort has this method already.or even_webview_post_message(message, ports)This eliminates the need for JavaScript side API which Chris's proposal is intended to remove, and is not too complex to use.Yep, this is much easier to use as you mentioned in the comment on the design doc. Thanks for the suggestion!On Fri, Mar 8, 2019 at 5:07 PM Shimi Zhang <ct...@chromium.org> wrote:I see your point, we are still trying to avoid chrome.runtime.postMessage() in JavaScript context. I was confused because my mind connects chrome.runtime.postMessage with addWebMessageListenerCompat() not setDocumentStartJavascript().Instead of adding chrome.runtime.postMessage(), we just supply a port for each frame, and in the implementation, we hide the detail that we have one pair of ports for each frame and let all of one end of them talk to the WebMessageListenerCompat, so to app developer, it looks still N:1 communication.I think that's probably doable, though I am not entirely sure about the lifecyle of the ports.One concern I have is that in this way, for web contents controlled by app developer case, they also need to do extra setup by calling setDocumentStartJavascript to get the port, so it is not as intuitive as chrome.runtime.postMessage(). Since addJavascriptInterface is so convenient to use, I feel it is hard to replace it with the new APIs we are going to provide.On Fri, Mar 8, 2019 at 4:30 PM Chris Harrelson <chri...@chromium.org> wrote:I see. Then instead of that, register a WebMessageListenerCompat like in your design, but connect it to each new frame via a bridge through a MessageChannel unique to each frame.On Fri, Mar 8, 2019 at 4:14 PM Shimi Zhang <ct...@chromium.org> wrote:The port will be available in only one frame, since the port will be neutered while passing to another context.On Fri, Mar 8, 2019 at 3:58 PM Chris Harrelson <chri...@chromium.org> wrote:On Fri, Mar 8, 2019 at 3:51 PM Shimi Zhang <ct...@chromium.org> wrote:It is not clear to me from the snippet- When did MessageChannel get created and from which side (Java/JavaScript)?- When one of the ports passed to the other side.Could you please be more specific?Let me try again, sorry for an incomplete snippet:String injectedJavascript = "var postMessageObject; function bootstrap(arg) { postMessageObject = arg; }";String initializeJavascriptMethod = "bootstrap";WebMessagePorts[] ports = webView.createWebMessageChannel(); // ports[0] is the Java end, ports[1] is the JavaScript end, I assume?// Inject |injectedJavascript|, then call |initializeJavascriptMethod| with a MessageChannel as an argument that has been bound to |ports|webView.setDocumentStartJavascript(injectedJavascript, initializeJavascriptMethod, ports);> String injectedJavascript = "var postMessageObject; function bootstrap(arg) { postMessageObject = arg; }";> String initializeJavascriptMethod = "bootstrap";> webView.setDocumentStartJavascript(injectedJavascript, initializeJavascriptMethod);On Fri, Mar 8, 2019 at 2:28 PM Chris Harrelson <chri...@chromium.org> wrote:On Fri, Mar 8, 2019 at 2:14 PM Torne (Richard Coles) <to...@chromium.org> wrote:That still only allows Java to initiate the communication, which means that for example you can't easily communicate with the embedding app after a renderer-initiated navigation. We want to replace addJavascriptInterface, which is persistent across page loads and doesn't require the page to do anything special to be able to call the methods, so not having that capability in the new system is likely to mean it's not used very often.Sorry, I mean to write:setDocumentStartJavascript(injectedJavascript, initializeJavascriptMethod)which would automatically inject into all frames created, like you said. Agree that that is a critical requirement.On Fri, 8 Mar 2019 at 17:10 Chris Harrelson <chri...@chromium.org> wrote:How about a slightly modified API that not only injects javascript at start into the desired frames, but also calls a specified method on that injected javascript to supply a MessagePort?String injectedJavascript = "var postMessageObject; function bootstrap(arg) { postMessageObject = arg; }";String initializeJavascriptMethod = "bootstrap";webView.runJavaScriptAtDocumentStart(injectedJavascript, initializeJavascriptMethod);At document start, the injectedJavascript would be run, and then the method initializeJavascriptMethod would be called with an argument equal to a Javascript object that is the MessageChannel, on which postMessage can be called. This keeps the MessageChannel out of the global namespace of JavaScript.Would this work?Chris
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CA%2BjkKukDORMFn5MoVXpOX0HfTnV%3DL7P5GfQWO4y72hAh_5fnaA%40mail.gmail.com.
To summarize our offline discussion around chrome.runtime.postMessage(), we decided to replace it with injecting a message port to JavaScript context and passing the entangled other end to Java side:Before:- JavaScript side:- chrome.runtime.postMessage(message, ports)- Java side- WebViewCompat#setWebMessageListener(WebView view, WebMessageListener listener, Uri[] alllowedOrigins)- WebMessageListener#onWebMessage(WebView view, Uri sourceOrigin, WebMessage message)After:- No JavaScript side API added.- Java side:- WebViewCompat#setWebMessagePortListener(WebView view, String portName, WebMessagePortListener listener, Uri[] allowedOrigins)- WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port)
On Tue, 12 Mar 2019 at 15:35, Shimi Zhang <ct...@chromium.org> wrote:To summarize our offline discussion around chrome.runtime.postMessage(), we decided to replace it with injecting a message port to JavaScript context and passing the entangled other end to Java side:Before:- JavaScript side:- chrome.runtime.postMessage(message, ports)- Java side- WebViewCompat#setWebMessageListener(WebView view, WebMessageListener listener, Uri[] alllowedOrigins)- WebMessageListener#onWebMessage(WebView view, Uri sourceOrigin, WebMessage message)After:- No JavaScript side API added.- Java side:- WebViewCompat#setWebMessagePortListener(WebView view, String portName, WebMessagePortListener listener, Uri[] allowedOrigins)- WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port)Sorry to jump on this so late, but this looks like it will be tricky to use unless I'm misunderstanding the intention. Is the flow going to work like this:1. Java code calls setWebMessagePortListener2. Pair of entangled ports gets created for every eligible frame that currently exists (matching allowedOrigins)3. onWebMessagePortInjected gets called once for each eligible frame giving the frame's origin and the port
If so, then:- What happens if you create a new frame? Does it get a new port created for it (as long as it's matching allowedOrigins) or is there no way to communicate with it without calling setWebMessagePortListener again?
- If you *do* call setWebMessagePortListener again with the same parameters does it recreate all the existing ports and overwrite the JS globals in existing frames? (that seems likely to be confusing)
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)
- How do ports ever get destroyed?
- How does the Java code distinguish between multiple ports that all have the same origin?
- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)
Thanks Torne for the review, replied inlineOn Mon, Mar 25, 2019 at 12:30 PM Torne (Richard Coles) <to...@chromium.org> wrote:On Tue, 12 Mar 2019 at 15:35, Shimi Zhang <ct...@chromium.org> wrote:To summarize our offline discussion around chrome.runtime.postMessage(), we decided to replace it with injecting a message port to JavaScript context and passing the entangled other end to Java side:Before:- JavaScript side:- chrome.runtime.postMessage(message, ports)- Java side- WebViewCompat#setWebMessageListener(WebView view, WebMessageListener listener, Uri[] alllowedOrigins)- WebMessageListener#onWebMessage(WebView view, Uri sourceOrigin, WebMessage message)After:- No JavaScript side API added.- Java side:- WebViewCompat#setWebMessagePortListener(WebView view, String portName, WebMessagePortListener listener, Uri[] allowedOrigins)- WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port)Sorry to jump on this so late, but this looks like it will be tricky to use unless I'm misunderstanding the intention. Is the flow going to work like this:1. Java code calls setWebMessagePortListener2. Pair of entangled ports gets created for every eligible frame that currently exists (matching allowedOrigins)3. onWebMessagePortInjected gets called once for each eligible frame giving the frame's origin and the portYes, that's correct.If so, then:- What happens if you create a new frame? Does it get a new port created for it (as long as it's matching allowedOrigins) or is there no way to communicate with it without calling setWebMessagePortListener again?If setWebMessagePortListener is already called on a WebView instance, a new port will be injected for a new frame and onWebMessagePortInjected should be called. We are going to do the injection in DidClearWindowObject as addJavascriptInterface does, I don't think we need to call setWebMessagePortListener again for this case.
- If you *do* call setWebMessagePortListener again with the same parameters does it recreate all the existing ports and overwrite the JS globals in existing frames? (that seems likely to be confusing)
See above reply.\
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)New pair of ports will be created.- How do ports ever get destroyed?JavaScript side port should be destroyed automatically if the frame is gone, Java side corresponding port will throw an exception when posting message if the JavaScript side port is not available anymore.
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.
- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)New pair of ports will be created.- How do ports ever get destroyed?JavaScript side port should be destroyed automatically if the frame is gone, Java side corresponding port will throw an exception when posting message if the JavaScript side port is not available anymore....so that would be "never" for the java side, which is bad. We'd need to at least notify them somehow so they can throw it out.
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.So you can reply on the same port when you receive a message, which means you can use it, but you can't initiate communication from the java side still in that case..
- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.Oh. I assumed from the description that it was WebMessagePortListener that would receive the messages, but I guess that only gets the callback about the port being created? That seems pretty hard to use still.
Is it actually useful to expose the ports in this way at all? It seems like a simpler API to just allow JS to initiate communication would be to just have something like:WebViewCompat.provideMessagePorts(WebView view, String portName, WebMessageCallback callback, Uri[] allowedOrigins)and just have it inject a port into every relevant frame, and when a message is received on any of those ports, call the callback. The callback gets the port as a parameter so the app can reply without having to ever keep the ports at all..Is there a use case that's only fulfiled by having the explicit callback when the port is injected?
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)New pair of ports will be created.- How do ports ever get destroyed?JavaScript side port should be destroyed automatically if the frame is gone, Java side corresponding port will throw an exception when posting message if the JavaScript side port is not available anymore....so that would be "never" for the java side, which is bad. We'd need to at least notify them somehow so they can throw it out.Right, but I don't think we have a way to send a notification if one end is closed, the port object is a wrapper of an integer, so the worse case I believe is to keep that integer around, and hope that is not too bad.
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.So you can reply on the same port when you receive a message, which means you can use it, but you can't initiate communication from the java side still in that case..I didn't get the question clearly, we have Java API to inject port to main frame already WebView#createWebMessageChannel() and then WebView#postWebMessage(), if that answers "initiate communication from the java side" part.
Also, our WebMessagePort API allows you to pass port too (inside of WebMessage), just use createWebMessageChannel() and pass one end to JavaScript side, so receiving a port from JavaScript side means you could send both string message and port.- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.Oh. I assumed from the description that it was WebMessagePortListener that would receive the messages, but I guess that only gets the callback about the port being created? That seems pretty hard to use still.Yep, we only get the callback when the port is being created.Is it actually useful to expose the ports in this way at all? It seems like a simpler API to just allow JS to initiate communication would be to just have something like:WebViewCompat.provideMessagePorts(WebView view, String portName, WebMessageCallback callback, Uri[] allowedOrigins)and just have it inject a port into every relevant frame, and when a message is received on any of those ports, call the callback. The callback gets the port as a parameter so the app can reply without having to ever keep the ports at all..Is there a use case that's only fulfiled by having the explicit callback when the port is injected?You may want to set different WebMessageCallback for different port, but since we have limited way to distinguish ports, that's pretty much equals to different WebMessageCallback for different origin.
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)New pair of ports will be created.- How do ports ever get destroyed?JavaScript side port should be destroyed automatically if the frame is gone, Java side corresponding port will throw an exception when posting message if the JavaScript side port is not available anymore....so that would be "never" for the java side, which is bad. We'd need to at least notify them somehow so they can throw it out.Right, but I don't think we have a way to send a notification if one end is closed, the port object is a wrapper of an integer, so the worse case I believe is to keep that integer around, and hope that is not too bad.I don't mean I'm worried about it leaking, I mean that there's no reasonable way for the java side to know anything useful about its state, if it's maintaining a map of ports or something.
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.So you can reply on the same port when you receive a message, which means you can use it, but you can't initiate communication from the java side still in that case..I didn't get the question clearly, we have Java API to inject port to main frame already WebView#createWebMessageChannel() and then WebView#postWebMessage(), if that answers "initiate communication from the java side" part.The existing APis only work for the top frame. So, yeah I guess there's just no way to distinguish frames so the only way to use the new port to initiate communication is to send a message to every port associated with an origin, maybe?
Also, our WebMessagePort API allows you to pass port too (inside of WebMessage), just use createWebMessageChannel() and pass one end to JavaScript side, so receiving a port from JavaScript side means you could send both string message and port.- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.Oh. I assumed from the description that it was WebMessagePortListener that would receive the messages, but I guess that only gets the callback about the port being created? That seems pretty hard to use still.Yep, we only get the callback when the port is being created.Is it actually useful to expose the ports in this way at all? It seems like a simpler API to just allow JS to initiate communication would be to just have something like:WebViewCompat.provideMessagePorts(WebView view, String portName, WebMessageCallback callback, Uri[] allowedOrigins)and just have it inject a port into every relevant frame, and when a message is received on any of those ports, call the callback. The callback gets the port as a parameter so the app can reply without having to ever keep the ports at all..Is there a use case that's only fulfiled by having the explicit callback when the port is injected?You may want to set different WebMessageCallback for different port, but since we have limited way to distinguish ports, that's pretty much equals to different WebMessageCallback for different origin.Well you aren't even telling the caller the origin in your callback as proposed :)
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)New pair of ports will be created.- How do ports ever get destroyed?JavaScript side port should be destroyed automatically if the frame is gone, Java side corresponding port will throw an exception when posting message if the JavaScript side port is not available anymore....so that would be "never" for the java side, which is bad. We'd need to at least notify them somehow so they can throw it out.Right, but I don't think we have a way to send a notification if one end is closed, the port object is a wrapper of an integer, so the worse case I believe is to keep that integer around, and hope that is not too bad.I don't mean I'm worried about it leaking, I mean that there's no reasonable way for the java side to know anything useful about its state, if it's maintaining a map of ports or something.Unfortunately no way to tell that :( I just checked the internal implementation (MessagePipeHandleImpl etc), we probably could only tell that port is closed if we called close() from Java side.
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.So you can reply on the same port when you receive a message, which means you can use it, but you can't initiate communication from the java side still in that case..I didn't get the question clearly, we have Java API to inject port to main frame already WebView#createWebMessageChannel() and then WebView#postWebMessage(), if that answers "initiate communication from the java side" part.The existing APis only work for the top frame. So, yeah I guess there's just no way to distinguish frames so the only way to use the new port to initiate communication is to send a message to every port associated with an origin, maybe?Oh I see the point, you meant if we get bunch of ports of the same origin and want to start the communication from Java side, we don't have a way to tell which port is associated with which frame. Yes the only way is to send a message to all the ports.. Which seems a little silly.. How about we distinguish iframe and main frame in onWebMessagePortInjected by providing a boolean isMainFrame? But I think there is another way, pass a JavaScript to setDocumentStartJavascript, it sends message to Java side, so Java could tell which frame it is talking to. I'd admit that is not very intuitive.
Also, our WebMessagePort API allows you to pass port too (inside of WebMessage), just use createWebMessageChannel() and pass one end to JavaScript side, so receiving a port from JavaScript side means you could send both string message and port.- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.Oh. I assumed from the description that it was WebMessagePortListener that would receive the messages, but I guess that only gets the callback about the port being created? That seems pretty hard to use still.Yep, we only get the callback when the port is being created.Is it actually useful to expose the ports in this way at all? It seems like a simpler API to just allow JS to initiate communication would be to just have something like:WebViewCompat.provideMessagePorts(WebView view, String portName, WebMessageCallback callback, Uri[] allowedOrigins)and just have it inject a port into every relevant frame, and when a message is received on any of those ports, call the callback. The callback gets the port as a parameter so the app can reply without having to ever keep the ports at all..Is there a use case that's only fulfiled by having the explicit callback when the port is injected?You may want to set different WebMessageCallback for different port, but since we have limited way to distinguish ports, that's pretty much equals to different WebMessageCallback for different origin.Well you aren't even telling the caller the origin in your callback as proposed :)We are passing sourceOrigin in WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port), so they could tell the origin of that port.
- What happens if frames navigate? (to the same origin, or to a different but still allowed origin)New pair of ports will be created.- How do ports ever get destroyed?JavaScript side port should be destroyed automatically if the frame is gone, Java side corresponding port will throw an exception when posting message if the JavaScript side port is not available anymore....so that would be "never" for the java side, which is bad. We'd need to at least notify them somehow so they can throw it out.Right, but I don't think we have a way to send a notification if one end is closed, the port object is a wrapper of an integer, so the worse case I believe is to keep that integer around, and hope that is not too bad.I don't mean I'm worried about it leaking, I mean that there's no reasonable way for the java side to know anything useful about its state, if it's maintaining a map of ports or something.Unfortunately no way to tell that :( I just checked the internal implementation (MessagePipeHandleImpl etc), we probably could only tell that port is closed if we called close() from Java side.If there's no way to know whether a port is still valid without sending a message through it then that's unfortunate, and makes it seem to me like we should *avoid* any API design choices that make it seem like you are supposed to keep references to ports around at all..
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.So you can reply on the same port when you receive a message, which means you can use it, but you can't initiate communication from the java side still in that case..I didn't get the question clearly, we have Java API to inject port to main frame already WebView#createWebMessageChannel() and then WebView#postWebMessage(), if that answers "initiate communication from the java side" part.The existing APis only work for the top frame. So, yeah I guess there's just no way to distinguish frames so the only way to use the new port to initiate communication is to send a message to every port associated with an origin, maybe?Oh I see the point, you meant if we get bunch of ports of the same origin and want to start the communication from Java side, we don't have a way to tell which port is associated with which frame. Yes the only way is to send a message to all the ports.. Which seems a little silly.. How about we distinguish iframe and main frame in onWebMessagePortInjected by providing a boolean isMainFrame? But I think there is another way, pass a JavaScript to setDocumentStartJavascript, it sends message to Java side, so Java could tell which frame it is talking to. I'd admit that is not very intuitive.I'm not sure how that JS would know how to identify the frame either? That seems like a generally hard problem.
Also, our WebMessagePort API allows you to pass port too (inside of WebMessage), just use createWebMessageChannel() and pass one end to JavaScript side, so receiving a port from JavaScript side means you could send both string message and port.- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.Oh. I assumed from the description that it was WebMessagePortListener that would receive the messages, but I guess that only gets the callback about the port being created? That seems pretty hard to use still.Yep, we only get the callback when the port is being created.Is it actually useful to expose the ports in this way at all? It seems like a simpler API to just allow JS to initiate communication would be to just have something like:WebViewCompat.provideMessagePorts(WebView view, String portName, WebMessageCallback callback, Uri[] allowedOrigins)and just have it inject a port into every relevant frame, and when a message is received on any of those ports, call the callback. The callback gets the port as a parameter so the app can reply without having to ever keep the ports at all..Is there a use case that's only fulfiled by having the explicit callback when the port is injected?You may want to set different WebMessageCallback for different port, but since we have limited way to distinguish ports, that's pretty much equals to different WebMessageCallback for different origin.Well you aren't even telling the caller the origin in your callback as proposed :)We are passing sourceOrigin in WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port), so they could tell the origin of that port.Ah, ok. I still think that's way more convoluted, though.. if you want a per-origin listener you can just call the main API multiple times with only one origin in each call. Is there a use case that's *only* possible if you have the onWebMessagePortInjected callback?
- How does the Java code distinguish between multiple ports that all have the same origin?Currently there is no way to distinguish that I believe.So you can reply on the same port when you receive a message, which means you can use it, but you can't initiate communication from the java side still in that case..I didn't get the question clearly, we have Java API to inject port to main frame already WebView#createWebMessageChannel() and then WebView#postWebMessage(), if that answers "initiate communication from the java side" part.The existing APis only work for the top frame. So, yeah I guess there's just no way to distinguish frames so the only way to use the new port to initiate communication is to send a message to every port associated with an origin, maybe?Oh I see the point, you meant if we get bunch of ports of the same origin and want to start the communication from Java side, we don't have a way to tell which port is associated with which frame. Yes the only way is to send a message to all the ports.. Which seems a little silly.. How about we distinguish iframe and main frame in onWebMessagePortInjected by providing a boolean isMainFrame? But I think there is another way, pass a JavaScript to setDocumentStartJavascript, it sends message to Java side, so Java could tell which frame it is talking to. I'd admit that is not very intuitive.I'm not sure how that JS would know how to identify the frame either? That seems like a generally hard problem.I was thinking js code could read something from the DOM tree, but that's not applicable everywhere.Also, our WebMessagePort API allows you to pass port too (inside of WebMessage), just use createWebMessageChannel() and pass one end to JavaScript side, so receiving a port from JavaScript side means you could send both string message and port.- Does the java code have to maintain a map of origins to ports to be able to send messages? (and how does it clean up entries from this map?)Probably not, for each port, you'll need to provide a WebMessageCallback to handle the communication.Oh. I assumed from the description that it was WebMessagePortListener that would receive the messages, but I guess that only gets the callback about the port being created? That seems pretty hard to use still.Yep, we only get the callback when the port is being created.Is it actually useful to expose the ports in this way at all? It seems like a simpler API to just allow JS to initiate communication would be to just have something like:WebViewCompat.provideMessagePorts(WebView view, String portName, WebMessageCallback callback, Uri[] allowedOrigins)and just have it inject a port into every relevant frame, and when a message is received on any of those ports, call the callback. The callback gets the port as a parameter so the app can reply without having to ever keep the ports at all..Is there a use case that's only fulfiled by having the explicit callback when the port is injected?You may want to set different WebMessageCallback for different port, but since we have limited way to distinguish ports, that's pretty much equals to different WebMessageCallback for different origin.Well you aren't even telling the caller the origin in your callback as proposed :)We are passing sourceOrigin in WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port), so they could tell the origin of that port.Ah, ok. I still think that's way more convoluted, though.. if you want a per-origin listener you can just call the main API multiple times with only one origin in each call. Is there a use case that's *only* possible if you have the onWebMessagePortInjected callback?Besides no Java initiated postMessage on ports, one other problem is that WebMessage is not very aligned with web spec, we don't have sourceOrigin for message at this moment, though I think that's doable in AndroidX.. So inside of WebMessageCallback, there is no way to tell which source origin the port/message is from.
Ah, that's true - you would almost certainly need to wait for the JS to message you anyhow since messages aren't queued.
Torne, I checked implementation for MessagePort's onmessage, then realized that we never set origin field for MessagePort.onmessage()'s MessageEvent on all platforms; The message port post message steps part from the spec didn't require to fill origin field either, comparing to window post message steps, which explicitly required to set the origin field. Looks like the current implementation of MessagePort.postMessage() doesn't have context of origin and adding this for WebView only doesn't seem like a good idea.. :(
Ah, that's true - you would almost certainly need to wait for the JS to message you anyhow since messages aren't queued.Haven't followed the rest of this thread, but if this is about a JS MessagePort object, messages are queued until .start() is called or something is assigned to onmessage. If this is exposed some other way in JS that might or might not be the case of course.
Hi blink-dev@,
After explored multiple design options, we had a new design for the API interface.
No JavaScript side API added
Inject MessagePort when needed from Java API.
Java side:
WebViewCompat#setWebMessagePortListener(WebView view, String portName, WebMessagePortListener listener, Uri[] allowedOrigins)
WebMessagePortListener#onWebMessagePortInjected(WebView view, Uri sourceOrigin, WebMessagePort port)
No JavaScript side API.
Inject an object when needed from Java API
Object method postMessage(message, transferList)
Object callback onmessage(event)
Java side:
WebViewCompat#onWebMessage(WebView view, WebEventTarget eventTarget, Uri sourceOrigin, WebMessageCompat message) in WebMessageListenerCompat interface
WebViewCompat#setWebMessageListener(WebView view, WebMessageListener listener, String handleName, Uri[] allowedOrigins)
WebEventTarget#postMessage(WebMessageCompat message)
The main difference is that previously we are injecting a MessagePort to web content, instead, now we are injecting a custom object, which has postMessage() and onmessage() callback.
This is mainly because:
MessagePort’s postMessage() doesn’t carry source origin, we want to make it as easy as possible for developers to use the API securely, so having a source origin on each JS -> Java message makes it easier to verify than having to maintain a map of which message ports correspond to which documents.
Having Java -> JS message arrive via a dedicated event listener avoids needing a dummy/reserved source origin for the Java app. A separate communication channel from the window object makes the JavaScript code less complex.
--
You received this message because you are subscribed to the Google Groups "blink-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to blink-dev+...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CAEV-rjcLeps-S%3DZBGQ_K750A8rDt%3Dd4UqV2wiaTNU_r-2To0Aw%40mail.gmail.com.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/blink-dev/CA%2BjkKu%3DKNE5HqvWnG%2BX31m0ACFqzm3RzsqiPPvpjh%2BupmF_BqA%40mail.gmail.com.