Fundamentally, the point of a WebView is to load URLs into it, and if we cannot guarantee the semantics of that it's going to cause pain for developers. Another way of thinking about this - what do we expect a developer to do if navigate returns null?
... wouldn't fix the problem of "the user called navigate (with a well formed URL), they expect a navigation to occur but for some reason it doesn't".
Yes, thanks Rakina for raising the question, and for providing so much useful context!Fundamentally, the point of a WebView is to load URLs into it, and if we cannot guarantee the semantics of that it's going to cause pain for developers. Another way of thinking about this - what do we expect a developer to do if navigate returns null?... wouldn't fix the problem of "the user called navigate (with a well formed URL), they expect a navigation to occur but for some reason it doesn't".I think these statements vastly oversimplify the concept of navigation, and they would cause fundamental problems if we encoded them into an API that can't meet those expectations.There cannot be a guarantee that a request to navigate to a URL will succeed. Developers can't ever rely on that expectation, and they already have to handle failure cases like the server being down (or throttles canceling it, etc, etc), though in most of those cases the NavigationHandle does get returned but later fails.
There's still a whole class of cases that Rakina mentions where the navigation doesn't get that far and we return null from LoadURLWithParams, and there is no expectation that this class won't expand over time. In fact, I'm reviewing a CL right now that expands it, by allowing enterprises to block additional debug URLs.
Importantly, it is our responsibility to only expose APIs whose invariants can be met, or else we end up stuck in an intractable state where problems occur and we can't fix them. We've already seen this with other Android WebView APIs, and we face long-term hacks within //content because apps do things like requesting re-entrant navigations (which can't be supported). This means that we should be very careful in exposing new navigation APIs, and discuss what can and can't be supported. I'm glad this is coming up while the API is still malleable enough to improve.I do agree that we should aim to expose APIs that are friendly to developers, and that means both (1) being ergonomic and having consistent ways to handle failures, and (2) not hiding issues that actually occur in practice. The fact that LoadURLWithParams has different ways of exposing different types of failures is admittedly not perfect. I'm open to discussions about how we expose these types of failures to developers (e.g., null from LoadURLWithParams always leads to an exception, or even having some type of failed NavigationHandle returned in those cases), but we can't expose an API that assumes the failures won't happen or even that they won't change over time.
On Thu, 15 May 2025 at 14:32, Charlie Reis <cr...@chromium.org> wrote:Yes, thanks Rakina for raising the question, and for providing so much useful context!Fundamentally, the point of a WebView is to load URLs into it, and if we cannot guarantee the semantics of that it's going to cause pain for developers. Another way of thinking about this - what do we expect a developer to do if navigate returns null?... wouldn't fix the problem of "the user called navigate (with a well formed URL), they expect a navigation to occur but for some reason it doesn't".I think these statements vastly oversimplify the concept of navigation, and they would cause fundamental problems if we encoded them into an API that can't meet those expectations.There cannot be a guarantee that a request to navigate to a URL will succeed. Developers can't ever rely on that expectation, and they already have to handle failure cases like the server being down (or throttles canceling it, etc, etc), though in most of those cases the NavigationHandle does get returned but later fails.Right, when there's a NavigationHandle returned which later fails this is fine - the API we are exposing can communicate navigation failures and also deals with navigations that succeed but do not commit. The navigation handle tells us a bunch about what happened, and many cases give us net::Error codes or other specific information about the failure that we can communicate to the app.There's still a whole class of cases that Rakina mentions where the navigation doesn't get that far and we return null from LoadURLWithParams, and there is no expectation that this class won't expand over time. In fact, I'm reviewing a CL right now that expands it, by allowing enterprises to block additional debug URLs.I wouldn't really have a problem with blocking all debug URLs in this API if that solves things for us. The case that seems important here is when we have a normal web URL that we expect to start a navigation, but LoadURLWithParams returns null for some reason we can't predict in the WebView implementation code.Importantly, it is our responsibility to only expose APIs whose invariants can be met, or else we end up stuck in an intractable state where problems occur and we can't fix them. We've already seen this with other Android WebView APIs, and we face long-term hacks within //content because apps do things like requesting re-entrant navigations (which can't be supported). This means that we should be very careful in exposing new navigation APIs, and discuss what can and can't be supported. I'm glad this is coming up while the API is still malleable enough to improve.I do agree that we should aim to expose APIs that are friendly to developers, and that means both (1) being ergonomic and having consistent ways to handle failures, and (2) not hiding issues that actually occur in practice. The fact that LoadURLWithParams has different ways of exposing different types of failures is admittedly not perfect. I'm open to discussions about how we expose these types of failures to developers (e.g., null from LoadURLWithParams always leads to an exception, or even having some type of failed NavigationHandle returned in those cases), but we can't expose an API that assumes the failures won't happen or even that they won't change over time.It's okay for there to be failures, but it's extremely hard to use an API that can fail without giving any reason. How we communicate that reason to WebView developers is an API design problem for us - a special navigation handle, an exception, we can figure that out ourselves.It seems like we only have three general options here:1. Enumerate all the reasons that LoadURLWithParams might return null and handle them all explicitly in our implementation, and //content folks promise not to intentionally create any new reasons without first having a compatibility conversation with WebView about it.2. Change the interface of LoadURLWithParams so that if it doesn't return a navigation handle it instead returns some kind of more-specific error code/message which WebView can communicate to the developer in whatever way makes sense for us.
On Fri, May 16, 2025 at 2:51 AM Torne (Richard Coles) <to...@chromium.org> wrote:On Thu, 15 May 2025 at 14:32, Charlie Reis <cr...@chromium.org> wrote:Yes, thanks Rakina for raising the question, and for providing so much useful context!Fundamentally, the point of a WebView is to load URLs into it, and if we cannot guarantee the semantics of that it's going to cause pain for developers. Another way of thinking about this - what do we expect a developer to do if navigate returns null?... wouldn't fix the problem of "the user called navigate (with a well formed URL), they expect a navigation to occur but for some reason it doesn't".I think these statements vastly oversimplify the concept of navigation, and they would cause fundamental problems if we encoded them into an API that can't meet those expectations.There cannot be a guarantee that a request to navigate to a URL will succeed. Developers can't ever rely on that expectation, and they already have to handle failure cases like the server being down (or throttles canceling it, etc, etc), though in most of those cases the NavigationHandle does get returned but later fails.Right, when there's a NavigationHandle returned which later fails this is fine - the API we are exposing can communicate navigation failures and also deals with navigations that succeed but do not commit. The navigation handle tells us a bunch about what happened, and many cases give us net::Error codes or other specific information about the failure that we can communicate to the app.There's still a whole class of cases that Rakina mentions where the navigation doesn't get that far and we return null from LoadURLWithParams, and there is no expectation that this class won't expand over time. In fact, I'm reviewing a CL right now that expands it, by allowing enterprises to block additional debug URLs.I wouldn't really have a problem with blocking all debug URLs in this API if that solves things for us. The case that seems important here is when we have a normal web URL that we expect to start a navigation, but LoadURLWithParams returns null for some reason we can't predict in the WebView implementation code.Importantly, it is our responsibility to only expose APIs whose invariants can be met, or else we end up stuck in an intractable state where problems occur and we can't fix them. We've already seen this with other Android WebView APIs, and we face long-term hacks within //content because apps do things like requesting re-entrant navigations (which can't be supported). This means that we should be very careful in exposing new navigation APIs, and discuss what can and can't be supported. I'm glad this is coming up while the API is still malleable enough to improve.I do agree that we should aim to expose APIs that are friendly to developers, and that means both (1) being ergonomic and having consistent ways to handle failures, and (2) not hiding issues that actually occur in practice. The fact that LoadURLWithParams has different ways of exposing different types of failures is admittedly not perfect. I'm open to discussions about how we expose these types of failures to developers (e.g., null from LoadURLWithParams always leads to an exception, or even having some type of failed NavigationHandle returned in those cases), but we can't expose an API that assumes the failures won't happen or even that they won't change over time.It's okay for there to be failures, but it's extremely hard to use an API that can fail without giving any reason. How we communicate that reason to WebView developers is an API design problem for us - a special navigation handle, an exception, we can figure that out ourselves.It seems like we only have three general options here:1. Enumerate all the reasons that LoadURLWithParams might return null and handle them all explicitly in our implementation, and //content folks promise not to intentionally create any new reasons without first having a compatibility conversation with WebView about it.2. Change the interface of LoadURLWithParams so that if it doesn't return a navigation handle it instead returns some kind of more-specific error code/message which WebView can communicate to the developer in whatever way makes sense for us.This might be workable. Do you think making LoadURLWithParams return a string reason if it's nullptr is enough for WebView, and that can be surfaced as an exception or something from the WebView API and that's easier to handle by the users? And there's no need to have the reasons enumerated (or at least easy to add new / arbitrary reasons after shipping)?
(Sorry for the delay in replying, was out unexpectedly)There's two types of things I can imagine potentially being added in the future:- A new web platform API is introduced that has certain rules on when a navigation can be triggered or not when that new API is involved.- A new internal policy is introduced to prevent existing ways to navigate, e.g. due to newly found security concerns.A generic WebView app that uses navigate() to go to a user-supplied URL, for example, can hypothetically go to sites using those new APIs, or be in a situation that falls under the new navigation-blocking policy. Would having those return null with "X API / policy blocks navigation from being navigated" as a reason be enough? (not sure if it's normal/reasonable for such an app to not handle the null case already)
IIUC, the API shape we're aligning towards is:
- NavigationController::LoadUrl returning something like Result<NavigationHandle, ErrorString>.
- WebViewCompat#navigate returning a @NonNull Navigation (based on that NavigationHandle) or throwing some Exception.
Throwing the exception is going to be more informative to the developer than just returning null, and practically they're both going to be as intrusive.Yeah, I think that's reasonable, provided that the WebView team is involved in the discussion before shipping.
// Introduce a new PendingNavigation class.
PendingNavigation nav = WebViewCompat.navigate(URL, ...);
// Modification to the existing WebNavigationClient.
WebNavigationClient navClient = new WebNavigationClient() {
// This is where a PendingNavigation turns into a Navigation.
void onNavigationStarted(PendingNavigation pendingNavigation, Navigation navigation) {
if (nav == pendingNavigation) {
mMyNavigation = navigation;
}
}
};
> Can you clarify what kind of involvement you think should happen?For any navigation change that could affect existing WebView apps, loop the WebView team in at the design phase to assess the impact and see if there are ways to mitigate it. And, in the unlikely case we can't reach a good conclusion, give us an opportunity to escalate.
I'm not too familiar with the WebView navigation re-entrancy problem you mention (maybe Torne is?). If this is a case of "we kept this around because that's how WebView#loadUrl used to work", then maybe with WebViewCompat#navigate we have the opportunity to get rid of that behaviour.
I'd like to avoid exposing Promises to the developer (how do we explain when they're fulfilled and the current API is more callback based). How about this (this is from an Android developer's PoV):// Introduce a new PendingNavigation class.
PendingNavigation nav = WebViewCompat.navigate(URL, ...);
// Modification to the existing WebNavigationClient.
WebNavigationClient navClient = new WebNavigationClient() {// This is where a PendingNavigation turns into a Navigation.
void onNavigationStarted(PendingNavigation pendingNavigation, Navigation navigation) { if (nav == pendingNavigation) { mMyNavigation = navigation; } } };That way we can still keep track of which Navigation results from which navigate call, we're not mixing up Promises with the existing callback architecture and we don't have to add a new concept for when a Navigation object starts being usable. Just using a straight Navigation object would still be simpler though.
On Wed, 21 May 2025 at 13:30, Rakina Zata Amni <rak...@chromium.org> wrote:On Tue, May 20, 2025 at 11:40 PM Peter Conn <pec...@google.com> wrote:IIUC, the API shape we're aligning towards is:
- NavigationController::LoadUrl returning something like Result<NavigationHandle, ErrorString>.
- WebViewCompat#navigate returning a @NonNull Navigation (based on that NavigationHandle) or throwing some Exception.
Throwing the exception is going to be more informative to the developer than just returning null, and practically they're both going to be as intrusive.Yeah, I think that's reasonable, provided that the WebView team is involved in the discussion before shipping.Some of the content-owners (the ones cc-ed in the thread) discussed this internally and the shape seems reasonable. Can you clarify what kind of involvement you think should happen? What we imagine this to look like is //content expose base::expected<NavigationHandle, ErrorReason> as the return value. The ErrorReason will be an enum that WebView handles all the cases of, and if we add a new reason that will automatically require an update on the WebView handling code, at which point WebView reviewers can decide what to do (piggyback an existing exception, create a new one, etc). (With this I think WebView can also avoid having to maintain URL checks on the WebView side, and just depend on the enum reason to tell you that the URL is invalid, etc)Also, a somewhat orthogonal question (but related to this new API) came from creis@, who recently looked into re-entrancy problems encountered by WebView apps when removing the beforeunload legacy PostTask (here, which was linked a bit earlier in this thread as well). The main problem there is we currently need to do a "fake" asynchronous step between creating a NavigationHandle and triggering some throttles/callbacks firing in the WebView code, because the WebView code can trigger navigations synchronously, deleting the NavigationHandle that triggered those callbacks. This navigation re-entrancy is not allowed to happen within //content code, thus a fake PostTask is used to avoid that situation. While the existing ways to create navigations will still be a problem, we're wondering if it makes sense to make this new API asynchronous / returns something like a Promise<NavigationHandle>? That will make it not depend on the creation being synchronous / avoid re-entrancy entirely, and if in the far future we somehow deprecate the old APIs, then maybe we can avoid the fake PostTask (very wishful thinking). (creis@, CMIIW!)Also, in general, I wonder if having more asynchronous / less coupling on the timing of things for navigation-related APIs make sense (e.g. maybe make onNavigationStarted/Redirected/Completed not synchronously called from the //content counterpart as well)? The downside is they're not going to be reliably timely, so maybe we can make exception for things that are important to be fired exactly on the time things change, e.g. onNavigationCompleted (which is fired when we change the document / url / etc). (This is pretty far away from the original topic of this thread though, so I'm happy to start another thread)