[MUSIC PLAYING]
NATE FISCHER: Hi, everyone.
My name's Nate.
And I'm here from the WebView team.
And I'd like to talk about some modern WebView best practices--
or as I like to call it, using WebView like it's
2018, because it is.
So before I dive into what's modern with WebView,
let's talk a little bit about what's old.
So WebView's been around since the very beginning.
It was added back in API level 1.
And it changed in a significant way starting with Lollipop.
It became updatable-- the implementation was.
And this was great, because it meant
that users could benefit from security fixes and bug fixes.
It would update every six weeks just like your browser.
But a lot has changed since then, too.
We've added 40 new APIs just to make it easier
to work with WebView--
to help developers.
OK, but what's really changed?
Well, when we look out at the ecosystem,
it seems like apps are kind of using WebView the same way
they've always used it.
And when you look at Stack Overflow,
the answers are outdated at best.
They certainly aren't best practices.
And oftentimes, they're just wrong.
But some of the blame is on our shoulders, too.
Our API docs are written like it's still API level 1.
And everything has changed since then.
So something that we did is we looked over the past year.
And we looked out at the Android ecosystem.
And we looked at the devices that are out there today.
And what we found is that, although we added--
for example, on Nougat, we added all these great APIs.
A lot of apps can't really take advantage of them,
because they only run on the Nougat devices.
And that's less than 50% of devices today.
And it's been over two years since it came out.
But what we also saw is that almost 90% of devices
are running Lollipop, which means that they're
taking WebView updates.
And it means that this WebView implementation on the device
actually implements all these new APIs.
They just aren't exposed on older platform levels.
So we thought, can we do better?
So over the past year, we worked on our AndroidX library.
We launched a new AndroidX library.
And we're pretty excited about it.
And the basic idea is that we're going
to bring all these brand-new developer APIs,
but we're going to try to give you
the device coverage you need.
And we're going to support Lollipop and above.
We're going to do this by leveraging WebView's update
cycle.
But we also spent a lot of time on this library
to make sure it's really usable.
You can actually use these APIs to do productive things.
So we designed them to be fairly straightforward to swap out
from the frameworks APIs.
So this all sounds fine.
But how can we actually use this to make your apps better?
So let's take an example.
Since the very beginning of Android,
we've given apps a lot of power to customize
WebView's behavior.
And in particular, we added a callback called
shouldOverrideURLLoading.
And the idea behind this callback
is that you could choose to, for certain navigations,
cancel these in the WebView, and dispatch them
to a different app, instead, as an Android intent.
So the example is you might have YouTube URL.
But that might be better suited in the YouTube app.
And this sounds great.
A lot of apps took advantage of this, actually.
But there was a problem with the API.
We didn't get it right the first time.
The issues that JavaScript can trigger navigations.
And what we found is that there's
some malicious JavaScript out in the wild
which actually tries to exploit this app behavior.
And from the user's perspective, they
might be reading some web content.
And all of the sudden, without necessarily their interaction,
it starts opening up some new Android app that they're not
trying to open.
So we actually already fixed this issue.
We fixed this back in Nougat.
The idea is we exposed this notion of user gesture.
Did the user actually trigger this navigation?
And it actually worked really, really well.
But it only works on these Nougat-and-above devices.
And these before-Nougat devices are still vulnerable.
So we thought this was a great candidate for our AndroidX
library.
The idea is we're going to expose this user gesture,
but we're going to do it all the way back to Lollipop.
And this means that you get this device
coverage for all these devices.
And you can provide the safe experience to your users.
But we also made sure that this would be really easy for apps
to override.
There'd be no confusion.
So I think we succeeded.
But let's take a look at the code.
So before Nougat, here's what a lot of app's code
might have looked like.
We're overwriting-- should override URL loading.
We're setting a WebView client.
And in the body of this, we might be dispatching intents.
And this is the insecure version of the API.
But back in those days, this was the best we could do.
Some of the better apps out there when Nugget was released
might have something like this.
So you're overwriting the old implementation from before.
And this is for those before-Nougat devices.
But for the new devices, you have this new implementation.
And we're checking user gesture.
And we're not launching intent if we don't have user gesture.
So this seems great.
But it only runs on a small number of devices--
even today, only 50%.
So here's how it looks with AndroidX.
And the first thing I want to point out
is that almost nothing changed on this slide.
And I think that's really beautiful.
It means all the code that you already
wrote to handle the old framework APIs--
that code's all the same.
The only difference here is that we're importing or WebView
Client Compact class for our AndroidX library.
And we're setting this compact client.
And the idea here is that, when you're
using the compact client, all these callbacks will get--
instead of only invoked on Nougat and above,
they'll actually get invoked all the way back to Lollipop.
So you're going to get the device coverage you
need to provide your users a safer experience without really
changing a whole lot of code.
This is just one example what we have to offer in AndroidX.
So I'd really like to encourage you
to go check it out, and see how this can
help you make your app better.
We're giving you the device coverage
you need to actually use these APIs.
But we're going to have a lot more APIs available.
Some of these are going to be small improvements
on classic APIs, like we've seen.
But some are going to be for entirely new features,
like Safe Browsing.
And the best part is that this isn't some soon-to-be-released
library.
This is already out there.
You can go home, and you can add this into your apps.
And it's ready to go.
So you can try it in our 1.0 release.
Now I'd like to take a minute and shift gears a little bit.
So we've looked at the AndroidX APIs.
And these seem great.
They're going to give us new APIs with pretty good device
coverage, almost 90%.
But what about these APIs that have been around forever?
These have 100% device coverage.
But some of them are kind of hard to use.
Even I struggle to use them correctly,
and I work on the team.
So a common use case that we've seen is loading in-app content.
And the idea here is that you want
to display some content in your application,
but you don't necessarily want to fetch it over the internet.
You want to just compile it right into your app.
But you also want to continue to build this content with web
technology.
You want to use HTML, JavaScript, CSS.
And WebView's had pretty good support for this.
In fact, we almost have too much support for this.
We have so many APIs that it's hard to figure out
which one is actually the right thing to use.
And some of them have some weird gotchas that
make them kind of hard to use.
So I thought maybe we could take a look at some of these APIs,
and talk about what's so tough about them,
and recommend some best practices.
You don't have to start from scratch with a new API.
But you can tweak how you're using these.
So the first API we can look at is loadData.
And the basic idea here is that this is going
to accept some HTML content.
It accepts it as a string.
And it's supposed to display this in the WebView.
But one of the gotchas is it doesn't really
accept HTML content.
It accepts encoded HTML content.
The idea is you need to escape some special characters.
You replace them with a percent sign and a code following it.
And we call this percent-encoding.
And this is the default configuration for the API.
But there's actually no framework API
to do the percent-encoding for you.
It's kind of an oversight.
But the end result is that what we've seen
is that developers tend to actually do
this percent-encoding by hand themselves.
And this is manual.
It leads to bugs.
And these bugs can have significant impacts
for your application.
One small bug might seem OK today,
but it might break in a future WebView update
if you forget to encode a particular character.
The other issue with loadData is this thing
called an opaque origin.
So when your content gets loaded,
it has what we call an opaque origin.
And this means that it's going to fail all same origin
checks in the web.
And these same origin checks are actually
critical to providing powerful web APIs securely.
Without these same origin checks,
you can't use great APIs like XML HTTP Request.
So what can we do with this API?
Well, first off, we actually escaped
some of these encoding-related problems.
It turns out this API has always accepted an alternate encoding
scheme called Base64.
And this actually isn't that special of an encoding scheme.
It's just a different scheme.
It's not necessarily better.
But it's kind of nice, because there's actually
a framework API which will do the encoding for you.
And it does it correctly.
Great.
So there's Base64.encodeToString.
This will take the content, and spit out the right answer.
And frankly, the only reason it's not documented
is because this came out in API level 8, which today
is ancient history but was still in the future at the time
of writing loadData.
But we can also take a look at these same origin restrictions.
So the way we recommend to get around this
is to use something called loadDataWithBaseURL.
And one of the nice things about this--
I think of it as a feature, not a bug--
is that it actually accepts content as is.
You can actually give it content that's even totally un-encoded.
You don't even have to worry about the Base64 stuff
if you use this API.
The other really nice thing about it
is that it has this thing called a baseURL.
And although you're displaying this HTML content you pass in
as a string, the baseURL configures this origin
that it operates with.
So you can control which origin you
get without disabling important security settings
just to make APIs work.
So how do we actually choose the right baseURL?
So this is something that even I struggle with when
I try to use this API.
I know it's the right thing, but I actually
don't know what the right thing is to pass to it.
So I actually thought, how about we go through some
of the common use cases?
So something that we've seen is that a lot of apps
use cached content.
This is content they may have downloaded
from the web over the internet.
But they're saving it for later.
And now when they show it, they need to show it
with the right baseURL.
And the right URL that you choose
is just the original URL that it came from.
This guarantees that, if it worked originally,
it's going to have the same exact origin.
And all the APIs are going to continue to work.
The other use case that we've noticed
is that apps tend to ship their own content, and display it
this way, which is great.
What we recommend is that you choose a real internet-type
URL--
one of these HTTPS or HTTP URLs--
and it should use your organization's real domain.
And the reason for this is so that you
can import other resources from your servers,
and use them in this content without worrying
about any same origin checks.
It'll all work.
Then there's the question of, well, do we use HTTPS or HTTP?
So here's a little rule of thumb.
We always want to encourage--
HTTPS is the secure protocol.
But if you do need to show insecure resources,
we'd recommend that you use the HTTP scheme, as opposed
to disabling important security settings just
to get your app working.
And as the last point, I want to urge apps
to avoid custom URL schemes.
So this is something that we've noticed has cropped up.
Apps will make up their own scheme, and use that.
But the problem with this is that the web standards
don't really expect custom URL schemes.
So they're very vague about how to handle this.
And it turns out that they wind up
getting handled very inconsistently.
And this can lead to surprising app breakage.
So if you can stick to one of the internet URL schemes,
you're going to have a much better time.
So hopefully, I've expressed that we really
do care about developers at the WebView team.
And we're trying very hard to make sure
that you have powerful APIs that have the device coverage you
need, while also paying attention
to some of these old APIs, and making sure
that we explain how these need to be used--
making sure that they're actually usable.
If you have any questions, me and my colleagues
will be around for the rest of today, as well as tomorrow.
And we'd be more than happy to talk to you about WebView usage
and what you need for your application.
Thank you very much.
[MUSIC PLAYING]