Intent to Implement: Improved Js Java interaction for Android WebView

732 views
Skip to first unread message

Shimi Zhang

unread,
Feb 27, 2019, 6:30:12 PM2/27/19
to blin...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org

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.


Shimi Zhang

unread,
Feb 27, 2019, 7:02:59 PM2/27/19
to blin...@chromium.org, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org

Kentaro Hara

unread,
Feb 27, 2019, 9:14:16 PM2/27/19
to Shimi Zhang, blink-dev, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org
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.)

--
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.


--
Kentaro Hara, Tokyo, Japan

Shimi Zhang

unread,
Feb 28, 2019, 4:49:34 AM2/28/19
to Kentaro Hara, blink-dev, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org
On Wed, Feb 27, 2019 at 6:14 PM Kentaro Hara <har...@chromium.org> wrote:
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?
So there are two cases:
- If the web content is controlled by the app developer, they could use chrome.runtime.postMessage() directly.
- otherwise app developer will have to inject JavaScript to utilize chrome.runtime.postMessage(), but not necessarily at document_start.

The hard requirement for running this script before any other scripts from web page is for overriding JavaScript APIs I think, that case should be rare,
and the script is only doing overriding in this case, which should be computational light. But since evaluateJavascript() API only works for main frame, overall
I think there will be a decent among of usage but not very high.


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.)
Yes we are aware of this risk, I think that's ok for us to add setDocumentIdleJavascript() too, but comparing with document_start, document_idle is a confusing concept and I think document_idle is not guaranteed to be called before any scripts from web content? We want to warn developers to do only necessary setup work with that script if that could mitigate the loading slowness for document_start. Unlike with extension, in WebView there are many other time points that app developer could inject JavaScript.

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.

Kentaro Hara

unread,
Feb 28, 2019, 6:01:37 AM2/28/19
to Shimi Zhang, blink-dev, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org
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?

If WebView already has ways to inject scripts during page loading, setDocumentStartJavascript() is not adding a new problem, so I won't object to that :)

Shimi Zhang

unread,
Feb 28, 2019, 6:08:30 PM2/28/19
to Kentaro Hara, blink-dev, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org
On Thu, Feb 28, 2019 at 3:01 AM Kentaro Hara <har...@chromium.org> wrote:
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?

The list is actually longer, but these are commonly used as the injection points, onPageStarted() and onProgressChanged() are called during page loading.

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?

I would assume a modern website starts to run JavaScript after window load event or document DOMContentLoaded event, so document_idle should be sufficient for most cases, but correct me if it was not that case.

Thanks for raising this concern :)

Kentaro Hara

unread,
Feb 28, 2019, 9:07:57 PM2/28/19
to Shimi Zhang, blink-dev, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org
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?

Sounds good to me!

Changwan Ryu

unread,
Mar 1, 2019, 12:43:43 PM3/1/19
to Kentaro Hara, Shimi Zhang, blink-dev, bo...@chromium.org, Changwan Ryu, des...@chromium.org, ted...@chromium.org
There may be some value for document_idle in subframe use cases, but I'm not sure if that's large enough to call for a new API. We will internally discuss this potential API change. Thanks for your input.

Shimi Zhang

unread,
Mar 5, 2019, 3:51:27 AM3/5/19
to Changwan Ryu, Kentaro Hara, blink-dev, bo...@chromium.org, des...@chromium.org, ted...@chromium.org
We also want to check a concern with Blink API owners:

Since we are adding chrome.runtime.postMessage(), does that consider as introducing fragmentation to the Web platform? Although IMO this is another form of window.postMessage if we viewed WebView as a WindowProxy.

Alex Russell

unread,
Mar 7, 2019, 3:30:14 PM3/7/19
to Shimi Zhang, Changwan Ryu, Kentaro Hara, blink-dev, bo...@chromium.org, des...@chromium.org, ted...@chromium.org
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.

Have you evaluated that option?

Regards


Torne (Richard Coles)

unread,
Mar 7, 2019, 4:02:54 PM3/7/19
to Alex Russell, Shimi Zhang, Changwan Ryu, Kentaro Hara, blink-dev, bo...@chromium.org, des...@chromium.org, ted...@chromium.org
On Thu, 7 Mar 2019 at 15:30 'Alex Russell' via blink-dev <blin...@chromium.org> wrote:
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.

We *do* have APIs to let the Java code send messages to the top frame using the existing infrastructure: they arrive as messages with the empty string as an origin, which is.. not entirely obvious or self-explanatory, but should be unforgeable by the web at least. The part we don't currently have is a way for the web page to initiate communication with the Java code: there's no object it can call postMessage on unless it first receives a message from Java that passes a message port over. This presents a lot of bootstrapping difficulties, because the Java code has to wait until it knows there is an event handler registered to *receive* the message it's going to send, and the two sides have to explicitly manage transferring a message port to use for subsequent communication.

App developers have used this very little, and we suspect at least part of the reason is that it's difficult. In the cases where we've had feedback about it, developers have told us that it's hard to use, and we've had numerous security issues reported to us that are just caused by apps using these APIs incorrectly and not properly checking origins/etc.

If there's a different way to allow the page to initiate the communication without adding new public APIs we'd like to hear it, but we haven't thought of one.
 

Shimi Zhang

unread,
Mar 7, 2019, 7:08:24 PM3/7/19
to Alex Russell, Changwan Ryu, Kentaro Hara, blink-dev, bo...@chromium.org, des...@chromium.org, ted...@chromium.org
Along with what Torne said, we want to initiate communication from JavaScript side to address the issue. But I am not entirely sure if I understood the question below correctly:

> 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.
Did you mean instead of adding chrome.runtime.postMessage(), we just utilize window.postMessage()? I understand that we could specify window.postMessage() a targetOrigin, so the message will be delivered to targets, but there is no origin for WebView Java (and browser) side. chrome.runtime.postMessage eliminated that argument completely.


Shimi Zhang

unread,
Mar 12, 2019, 3:35:49 PM3/12/19
to blink-dev, Chris Harrelson, Torne (Richard Coles), Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
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)

The new way will inject a port to each allowed frame with the given portName argument, and when we done with injecting that port, Java side will get the callback to receive the entangled other end.

Benefits of the new way:
- Both Java and JavaScript code will be more concise, since app developer don't need to deal with port initiation.
- Removed chrome.runtime.postMessage() JavaScript API, since that is a concern to introducing fragmentation to the Web platform.
- App developers have more control of the scope of a port over chrome.runtime.postMessage by using setDocumentStartJavascript() to hide it from main context.

I'll figure out how to do the injection. Thanks for the discussion and suggestions!

On Mon, Mar 11, 2019 at 11:41 AM Shimi Zhang <ct...@chromium.org> wrote:
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

Chris Harrelson

unread,
Mar 12, 2019, 4:57:40 PM3/12/19
to Shimi Zhang, blink-dev, Torne (Richard Coles), Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
This new plan LGTM!

Once it's done and ready to ship, it'd be great to send a PSA to blnk-dev with a link to the developer docs, and also file an entry at chromestatus.com. This is just to make everyone aware and help the developer community keep track of things.

But I don't think it is necessary to get 3 LGTMs for this new plan. My reasoning is that, after this feature ships:
a) A default WebView does not have any developer-visible change
b) Android app developers can already inject arbitrary code / symbols into their WebView pages in the global javascript namespace, and this is just one more instance of that


Torne (Richard Coles)

unread,
Mar 25, 2019, 3:30:25 PM3/25/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
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 setWebMessagePortListener
2. 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?)

This seems underspecified and pretty difficult to use no matter what the answers to most of those questions are..

Shimi Zhang

unread,
Mar 25, 2019, 4:39:32 PM3/25/19
to Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Thanks Torne for the review, replied inline

On 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 setWebMessagePortListener
2. 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
Yes, 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.

Torne (Richard Coles)

unread,
Mar 25, 2019, 4:55:05 PM3/25/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
On Mon, 25 Mar 2019 at 16:39, Shimi Zhang <ct...@chromium.org> wrote:
Thanks Torne for the review, replied inline

On 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 setWebMessagePortListener
2. 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
Yes, 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.

OK - it wasn't clear that that was the case from the description.
 
  
- 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.\

Well the question still applies - what *does* happen if you call it more than once and use the same portName?
 
 
- 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?

Shimi Zhang

unread,
Mar 25, 2019, 5:36:31 PM3/25/19
to Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
The following calls of setWebMessagePortListener will bascially change the listener, portName and allowedOrigins, it won't affect existing ports.
 
 
- 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. 

Torne (Richard Coles)

unread,
Mar 25, 2019, 5:45:51 PM3/25/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Will it change things in the frames that currently exist, though?
 
 
 
- 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 :)

So yeah I still don't think there's any actual reason to expose the creation of the ports directly here.

Shimi Zhang

unread,
Mar 25, 2019, 6:17:15 PM3/25/19
to Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Every setWebMessagePortListener call will take effect since next navigation (DidClearWindowObject), existing frames will have DidClearWindowObject only when it started a new navigation, so if the web contents stays the same, there is no change for already injected ports.
 
 
 
- 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.

Torne (Richard Coles)

unread,
Mar 25, 2019, 6:26:45 PM3/25/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Oh - I asked earlier if it would create it for all the eligible frames that currently exist and you said yes, but I guess not then? That makes it a bit different, so this is just setting a set of configuration parameters and not actually causing anything to happen directly. Makes sense..
 
 
 
 
- 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?

Shimi Zhang

unread,
Mar 25, 2019, 7:19:39 PM3/25/19
to Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Sorry for the confusion :P, right, it is just setting configuration parameters and not doing port injection at that moment.
 
 
 
 
- 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..
I guess you meant for the port parameter in onWebMessagePortInjected()? That's mostly for calling WebMessagePort#postMessage(), otherwise, we need to wait until WebMessageCallback get called to be able to call that API on ports, i.e. no Java initiated postMessage() on ports.
 
 
 

- 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.

Torne (Richard Coles)

unread,
Mar 26, 2019, 12:50:11 PM3/26/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
I guess but I think it would still be simpler to be able to directly associate the callback, so that if you don't care about having a reference to the port you don't have to do it in two stages.
 
 
 
 

- 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.

I think we should just fix that and make WebMessage carry an origin, that seems like a problem already. Making the developer maintain a map of ports to origins just to know what origin a particular message came from also seems unpleasant.

Basically, I think the "simplest" use of this API should be just as easy for the java developer as if we had simply added chrome.runtime.postMessage or similar, without any need to really think about the details of how ports work. The developer should just be able to say "i'm interested in receiving messages from these origins" and then get callbacks when those origins send it a message, and anything more complex should be optional.

Shimi Zhang

unread,
Mar 26, 2019, 2:42:37 PM3/26/19
to Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Agreed. In turns of Java initiated postMessage() call, I feel that won't have any real usage. Because when injecting the port in DidClearWindowObject, the web page hasn't finished loading yet, if Java calls postMessage on port immediately after it receives onWebMessagePortInjected, there is probably no JavaScript side onmessage listener registered yet, so it is still better for JavaScript tells Java when it's ready to use.

Torne (Richard Coles)

unread,
Mar 26, 2019, 2:50:17 PM3/26/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
Ah, that's true - you would almost certainly need to wait for the JS to message you anyhow since messages aren't queued. So, yeah, I think the general design makes sense with the specified variable name and origin whitelist (instead of making it a "real" global as the original proposal said), but we should try to simplify the Java side API a bit to serve what seems like the primary use case. And, yeah, we should definitely add the origin to WebMessage on the Java side because otherwise it's easy for people to make security mistakes as soon as they start trying to communicate with more than a single whitelisted origin..

Shimi Zhang

unread,
Mar 27, 2019, 5:13:01 PM3/27/19
to Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
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.. :(

Marijn Kruisselbrink

unread,
Mar 27, 2019, 5:16:03 PM3/27/19
to Shimi Zhang, Torne (Richard Coles), blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu


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.

Torne (Richard Coles)

unread,
Mar 27, 2019, 5:34:26 PM3/27/19
to Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
On Wed, 27 Mar 2019 at 17:13, Shimi Zhang <ct...@chromium.org> wrote:
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.. :(

I don't think that's the API that we actually want to be emulating, though. We want this to behave like posting messages to a window handle; it's just that the handle is for "the embedding app" instead of another window inside the web content. The reason MessagePort.postMessage doesn't use origins is because it's used when you are explicitly passing the port around, but what I'm proposing here is that we *avoid* the app developer having to think of it in those terms because it's hard to use - and if they actually were okay with thinking of it in those terms, they could more or less do it already with our current APIs.

Torne (Richard Coles)

unread,
Mar 27, 2019, 5:34:55 PM3/27/19
to Marijn Kruisselbrink, Shimi Zhang, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
On Wed, 27 Mar 2019 at 17:15, Marijn Kruisselbrink <m...@chromium.org> wrote:


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.

Oh, that's actually really interesting and suggests we *should* be able to support initiating it from the java side just fine, then.. 

Shimi Zhang

unread,
May 7, 2019, 5:26:16 PM5/7/19
to Torne (Richard Coles), Marijn Kruisselbrink, blink-dev, Chris Harrelson, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu

Hi blink-dev@,


After explored multiple design options, we had a new design for the API interface.


The previous design:

  • 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)

    New design:

    • 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.

    Chris Harrelson

    unread,
    May 19, 2019, 11:24:44 PM5/19/19
    to Shimi Zhang, Torne (Richard Coles), Marijn Kruisselbrink, blink-dev, Changwan Ryu, Alex Russell, Ted Choc, Bo Liu
    For the record: this new plan sounds fine to me.

    Reply all
    Reply to author
    Forward
    0 new messages