Good afternoon, everyone.
My name is Anders, I am an Engineer on the Safari and WebKit Team, and I'll be joined later by my colleague Beth.
And this session is Session 206, Introducing the Modern WebKit API.
We've got a lot of really cool things to show you, so let's get started.
So first, I think I want to give you a brief overview of what WebKit actually is.
One way you can think of it is that WebKit is the layout and rendering engine behind Safari on Mac and iOS.
Another way to look at it is that WebKit starts where the Safari user interface ends.
But WebKit is also used by a lot of other apps, such as mail for displaying rich HTML email.
And iBooks for rendering gorgeous ebooks.
And even applications you wouldn't normally think were using WebKit, like messages for its conversation view.
And of course most important of all, your apps.
You've done some really amazing things with WebKit.
So if you're using WebKit in your app today on iOS, you're using UIWebView.
So let's have a quick show of hands, how many of you are using UIWebView in an app today?
Wow. On iOS X, you will be using a WebView.
And how many of you are using a WebView?
Cool. Now, over the years, we've received a lot of requests from you especially on iOS for something a bit more powerful.
And we really think we have something that you're going to like, and that is the modern WebKit API.
So what are you going to learn today?
You'll see how to adopt the modern WebKit API and to use it in your apps.
We'll take a look at few new cool features and how to integrate those in your app.
And then we'll dive a bit deeper and you can see how you can customize the web WebKit loads web pages, and even customize the web content itself.
So the modern WebKit API.
Class for class, this API is exactly the same on iOS and OS X.
Yeah, that's pretty cool.
[ Applause ]
Of course there are some minor changes due to the way we hook into AppKit and UIKit, but it's pretty much the same.
We're also taking advantage of the latest and greatest language and cocoa features, so you can spend less time writing glue.
code and more time focusing on making your app great.
And we've tried really hard to reduce the surface area while still giving you access to a ton of cool features.
And we're using the multi-process architecture that we're also using on Safari on both OS X and Lion, and now on iOS with iOS 8.
In fact, one of the goals we had with this modern API was to take a lot of features that were previously only available to Safari and give you access to them.
For example, super smooth 60 frames per second scrolling using hardware acceleration and Core Animation.
[ Applause ]
And this includes the fourth-tier compiler, that Greg was telling you about yesterday.
We've taken the back/forward swipes and the pinch to zoom gestures and from Safari and built them right into WebKit.
And we have a really cool way for your app to talk to web pages and vice versa.
Now, I mentioned that WebKit is multi-processed.
And what does this really mean?
It means the web content runs in a separate process completely isolated from your app.
Now, why is this a good idea?
Well, it's great for responsiveness.
Let's say you have loaded a web page and the web page is running a lot of scripts and doing a lot of layout, your app will still stay responsive even if this happens.
It's also really good for battery life and power usage.
Since each web page runs separately in its own process, we can put individual web pages into a low power mode for things like background tabs and fully-occluded Windows.
Now, this is also completely transparent to you, when you create a WKWebView, that is our new WebView class in the modern API, we'll spin up a web content process.
When you create another WKWebView, we'll spin up another web content process.
Now, we do this up to a limit and then when you create more WKWebViews, they'll share a web content process.
And when you deallocate your WKWebViews, we tear down the processes for you and you don't have to worry about a thing.
So let's talk about how to adopt the modern WebKit API.
So Beth and I are both huge Wikipedia fans.
We love reading Wikipedia, we read it all the time, and so we figured it would be a kind of cool idea to write a dedicated Wikipedia browser app using the modern API.
This is what it looks like.
It's called WKPedia.
Now, if you think this is just a WKWebView inside a [inaudible] Window, you're absolutely right.
But don't worry, we're going to add some more features to it.
But how did we create this WKWebView and how did we load the web page?
Well, creating a WKWebView is really easy.
Just as with any other view, you call alloc and then you call it into a frame, and that's it.
And how do you load a web page?
You get the URL, you get the URL request and then you call WKWebView load request, and that'll load the web page for you.
So that's all good if you have an app with a single WebView.
But for WKPedia, we really want to have support multiple windows so you can have more than one article showing at once.
And when you do this, there's always some state that you want to share between your web views, such as preferences, the set of processes to create your web pages in, and this set of links.
And the way to do this is to create a single configuration object and then when you create your WKWebViews, you pass along the configuration and that'll ensure that all the state is correctly shared between the web use.
This is what it looks like in code.
First, you create your WKWebView configuration and then you create a WKWebView, but you use an input frame configuration and you give it a configuration, and that's all you have to do.
Okay, back to WKPedia.
Now, even though this is not going to be a full-fledged browser, there's still some browser-like features that we want to add, such as a back/forward button, a title and a progress indicator.
So let's take a look at how we would do this.
And let's go from left to right and start with the back/forward buttons.
For this, we have a bunch of actions methods on WKWebView.
Here are some of them.
You can hook these up in interface builder to your UI elements without having to write any code.
And so for WKPedia, the ones we're interested in are go back and go forward.
And these are also auto-validating, which means that when you have hooked these up to your UI, it will actually update the enable/disable state based on whether you can go back or forwards.
So you don't have to worry about that.
So what about the title and the progress indicator?
For that we have a bunch of properties on WKWebView.
Here are some of them.
These are Cocoa key value observing compliant, which means that you can use the normal Cocoa KDO methods to listen for any changes to the property values.
And if you're on OS X, you can use Cocoa bindings to hook them up to your UI without having to write a single line of code.
So for WKPedia, we're interested in the page title, just the title property, and whether the page is loading or not, and that's the loading property.
Okay, so WKPedia is really starting to come along here, but it's still not a dedicated Wikipedia browser.
Now, what do I mean by that?
Well, let's say you go to the WebKit article in Wikipedia and you click this link to the WebKit homepage.
That'll open the link inside the WKPedia browser, but we want this to be a dedicated Wikipedia app and we don't want to load any external links.
So what we want to do is customize the way pages are loaded.
But before I can tell you about that, I need to explain how page loading actually works in WebKit.
So first, something happens that triggers a page load, this can be one of many things.
And as we saw earlier, it could be you calling WKWebView load request.
Then we send a request off to the server and we get back a response.
This could be a positive response or the server could send back a 404, which means, sorry, I don't know what this resource is, file not found.
And then, the server sends back some data and we're done.
Now, what you can do with WebKit is to have your app sort of inject itself after the action and response phases.
And to decide whether to continue the load or whether to cancel it.
And the way you do this is by implementing methods on the WKWebView navigation delegate.
There are 2 methods that correspond to the 2 different phases.
The first one is decide policy for navigation action, for the action phase.
The second one is decide policy for navigation response, for the response phase.
And with both of these delegate methods, you get data objects that contain enough information for you to be able to make an informed decision about whether you want to continue the load or not.
So for decide policy for navigation action, that object is a WK navigation action object.
It has properties.
For example, the navigation type lets you find out which type of navigation actually started the load, like if it was a link being clicked or if it was a go back, go forward request, and so forth.
Request is the request that we're going to send to the server.
And with the modifier flags property, you can even tell if the user held down shift or command or option when clicking the link.
For decide policy for navigation response, you get a WK navigation response object.
This also has some properties, response is the HTTP.
response that we get back from the server.
And in addition to these data objects, you also get a decision handler in the form of a block.
This is how your app can decide whether you want to go through with a load or cancel it.
And you do this by calling the block with 1 of 2 values.
So for decide policy for navigation action, you either pass WK navigation action policy cancel or allow.
And for decide policy for navigation response, you pass either WK navigation response policy cancel or allow.
Now, you can either call these blocks right away or you can call them sometime later, which can be really useful if you want to put up some UI and let the user decide whether the load should continue or not.
So now I'd like to ask my colleague Beth up on stage to show WKPedia and how to add some of these features.
[ Applause ]
Hi everyone, I'm Beth Dakin.
Anders and I work together on Safari and WebKit, but lately we've been working on our pet project, WKPedia.
So I have a super bear-bones version of the app to show you, and together we'll use all of the information that Anders just gave us to build it up and turn it into a dedicated browsing app.
So I'll show you what we have and then we'll work on improving it.
Just going to build and run.
Here we go, this is WKPedia as it stands.
So that's great but I'm clearly missing some basic browsing features.
If I look at the same website in Safari, for example, if I click on a new link, then I get some progress indication at the top, indicating how the load's going.
The URL bar gives me some indication of what page I'm on.
I can go back.
These are things that we clearly want in our app for it to feel like a web browser.
So let's add them.
I'll give you a quick tour of the code first.
This is our main class, it's a browser window controller, and that's an NSWindowController.
And if we look at the implementation file, you'll see this is where we have a property for the WKWebView.
And down here in window did load, this is where we allocate our WebView and it's where we load our initial request.
Okay, but we want to add some toolbar items, so let's go into interface builder.
I need to get my toolbar here, there we go.
First I want to make the toolbar visible at launch, and then we'll double click it to start adding some items.
So first we wanted a back and forward button, so I will drag those in.
And if we go over to this panel here, let me highlight one of these buttons, here I'll zoom this up for you.
You can see I've already hooked these buttons up to the appropriate method, so the back button's already hooked up to the go back action method, and the forward button's already hooked up to go forward, so those should work.
Then we want some indication of the page that we're on.
So we don't want our URL field here, because we want this to be a dedicated browser app, we don't want people to type in random URL's, they'll use this search field and Wikipedia to get to different pages.
So we just want some texts, but we want it to reflect the title of the Wikipedia page that we're actually on.
So that's very easy.
Over here let me zoom this up again.
So for this text, we just want to bind it to the file's owner, which is that browser window controller class that I showed you a minute ago.
And then we want to set the model key path to WebView dot title.
And so that's one of those KVO-compliant properties on WKWebView that Anders was telling us about.
So this should be all that we have to do, this should actually update whenever the title value changes, just get all of that KVO goodness.
All right, and finally, we just want some progress indication.
So I'll drag in the spinner too.
Okay, that looks about right.
So let's see how that works, let's build and run.
Okay, so we have some progress here.
I'll zoom up a little.
We have back/forward buttons, great, we'll see if they work in a second.
We have a title that reflects the page that we're on, San Francisco.
Our spinner doesn't seem to be doing quite the right thing, it's just kind of always there, and it's not indicating any load progress yet.
I navigate to a new page, great, the title updated right away to reflect San Diego.
Click my back button, and we went back, awesome.
Okay, so we've made a lot of improvements.
We need to fix that spinner.
I also feel like this title is really long and unnecessary.
They all seem to have this suffix, Wikipedia, the free encyclopedia.
I think it would be more useful in our app if we truncate the title down just to be the beginning part.
So we can handle that very easily in interface builder as well.
So let's make those two fixes.
First let's fix that spinner.
To fix that we want to go over here again to this panel to the animation section, and we want to bind to the file's owner again.
Again, that's our browser window controller class.
And here the model key path we WebView dot loading, yes.
And again, that's one of those KVO-compliant properties.
That should be all we have to do to get this to animate whenever WebView dot loading is true.
There's one other thing I want to do with this though, which is over here, I want to uncheck display when stopped.
So then it should go away when it's not animating.
Okay, and now we want to truncate that title also.
So I did already write some code that would do that.
Down here if we look in here, so this is just some simple code that I wrote that would take an NS string and see if it had this as a suffix at the end, and then return a string that doesn't have that part.
So I already wrote some code to do that and I can invoke it from within interface builder right over here using the value transformer option.
Delete Wikipedia title snippet value transformer.
So that will take the title and invoke this method with the title and then the result will be what's reflected in the toolbar.
So let's save, build and run again.
Okay, so progress has been made.
Let's try clicking another link.
We get our spinner.
It goes away when we're gone.
Our title is now a much smaller more useful thing for our app.
We still have our back/forward buttons.
This is looking great, this is really feeling like a browser.
But I haven't done anything yet to make it a dedicated Wikipedia browser besides just not providing a URL field.
But if I go to a link like this, and click on it, it's going to still open right in my app, which is not the behavior that I'm looking for that sort of defeats the purpose of this being a dedicated Wikipedia browsing app.
So we can easily fix this by implementing the navigation delegate just like Anders taught us.
So let's do that right now.
Let's go back to the code.
Okay, so first, I want to add 1 line of code to window did load here, to set the navigation delegate.
Here I'm setting the navigation delegate to self, so this way WebKit will know to look here in my class to see if there's any implementations of these delegate methods.
And then we want to implement 1 of the 2 delegate methods.
We want to implement decide policy for navigation action.
So let's quickly step through this.
So first I want to get the URL that the app wants to navigate to, and then if the host URL does not have a Wikipedia.org suffix, so this is a non-Wikipedia link, then in that case, I want to pass the navigation off to the default browser on the system.
So that's what will happen with this line of code.
Safari should open that link instead.
But we still need to tell our WKWebView that it shouldn't go ahead with the navigation also, so we'll invoke the decision handler block with the WK navigation action policy cancel value.
And while we were here I thought it would be cool just to add a feature to command click a link and open it in a new window in my own app.
So that's what I added down here.
So first I'm finding out if this navigation is happening with the command key down, and if it is, then I'm going to create a brand new browser window controller and I'm giving that this navigation request.
And then again, since I don't want this WKWebView to also navigate to the new page, I invoke the decision handler block with the cancel value.
And otherwise if we didn't fall into any of those situations, then we'll invoke the decision handler block with allow.
So let's save and build and run.
Okay, great, so regular page loading still works, so that's good.
Let's see, let's click on this link, and awesome, we successfully handed that navigation off to the default browser.
And if I command click a link, I get it in a new window in my own app.
Each of these are in its own process because this is modern WebKit API, pretty cool.
So this is awesome, in just a few minutes, we have built up a dedicated browser app using the modern WebKit API.
But I still feel like there are few modern features that I'm lacking here.
For example, again, if we look back in Safari, who really uses the back button anymore, I mean, I typically use the swipe back gesture to go back.
And I also want to be able to double tap to zoom and pinch out.
If I try to do those things in my app, nothing happens.
Double tap, no, nothing's happening.
But it's really, really easy to add these features.
So I'm going to do it really quickly, even though Anders hasn't shown us how to do it quite yet.
But we just need to add 2 lines of code.
Back up here in window did load.
Okay, so this first line of code allows back/forward navigation gestures, set that to yes, that gets you the back/forward swiping.
And allows magnification, that will get double tap and pinch to zoom.
Save that, build and run.
Okay, so now if I go forward, should be able to swipe back, awesome.
Can double tap, pinch right out, awesome.
So there you have it, we've built a modern we've used the modern WebKit API to build a dedicated Wikipedia browser app with modern filling features in just a few minutes.
So I'm going to hand it back to Anders, he's going to teach you a lot of the more advanced features that you can do with this API.
And I'll be back up later to show you how you can integrate some of those features into our iPad version of WKPedia.
Back to you, Anders.
[ Applause ]
That was really cool.
I especially like the extra touch at the end where you added adjuster support.
Let's recap how Beth did that.
So for the navigation gestures, that's the swipe to go back and forward, you just set the allow the stop forward navigation gestures property to yes on your WKWebView.
For zoom gestures on OS X, you set the allow magnification property to yes.
On iOS, this is handled by the underlying UI scroll view, and it's already on by default, so you don't have to do a thing.
So let's talk about WKPedia for iPad.
This is what it looks like.
It's really great, I use it all the time for reading Wikipedia articles on my way to work.
But I've noticed something that bothers me a little.
See, I like to read certain sections of an article, and that means that I have to go find the table of contents, I have to scroll down to it.
I find a section that I want to read, I tap on that link.
And then when I've read it, I need to scroll all the way back up or swipe back and look for another section and read that.
So I told Beth about this and she said, well, why don't we use some of the more advanced features of the modern WebKit API to take care of this?
Like why don't we just get rid of the table of contents from the web page?
And while we're at it, we can also get rid of this side bar to the left, to make room for the article.
And then we can put the table of contents natively in the UI, so we have this little button in the top left corner, and when you tap it, you get the table of contents in a native UI table view.
And when you're in landscape mode, you can always have it visible because of the extra space.
So what we really want to do here is customize web page contents from a Wikipedia.
And we want to use two features to do this.
One is called user scripts.
The other one is called script messages.
These are both handled by an object called WK user content controller.
WK user content controller is part of the configuration.
So it's a property on the WKWebView configuration called user content controller.
So let's talk about user scripts.
This will happen automatically for every web page.
So you don't have to manually call it every time the page loads.
So when you add user scripts, you need to consider two things.
The first one is when you want your user script to run.
You can either run your user script at document start time.
This is right after the document element has been created but before any other document has been parsed.
Or you can run them at document end, which is after the document is finished parsing but before any subresources such as images have necessarily finished loading.
This corresponds to the down content loading event, load event.
You also want to consider where these scripts should run.
You can run them either for all frames, so both the mainframe and the subframes, or just for the mainframe only.
So how do you create a user script?
First, you get the script string from somewhere.
In my case, I just have it as a string literal in my code.
Then you create a WK user script object and pass the source code and here I've highlighted the when and where.
So we want this user script to run at document start time but only for the mainframe.
And then you tell the user content controller to go ahead and add the user scripts, and that's it.
Now, what can user scripts do?
User scripts can do anything that scripts running on the web page can do.
So that includes using the down API to change the document structures.
Adding event listeners for onload events or click events or keyboard events or any event.
You can load external resources like images and even XML-HTTP requests.
And you can also communicate back to the application, and this is where script messages come in.
So script messages are messages that user scripts send to the application.
They consist of JSON data, so objects, arrays, strings, etcetera.
When you send them from your web page, we automatically convert them to the corresponding objective C-types.
So N as dictionaries, N as arrays, N as strings, etcetera.
So when you want to listen for script messages in your app, you want to register a script message handler.
This involves creating an object that conforms to the WK script message handler protocol.
This protocol has 1 method that you have to implement that receives script message.
And then you tell the user content controller to add the script message handler and you give it a name.
And this name is really important because it is how your web page communicates back to you.
So when you've added a script message handler, we express it window dot WebKit dot message handlers dot your name dot post message function.
Here's how you use this post message function.
You get your message in JSON form from somewhere, and then you call post message and you pass along the message.
So on the receiving side, WebKit will call it receives script message on the correct user script message handler, and the body property of the WK script message object that is passed along here will have your message automatically converted.
There are also some other properties in WK script message, you can find out which web page posted the message, and the name that was used to post the message.
One interesting thing about script messages is that it's just not user scripts that can send script messages.
Your web page can also send messages.
This is really cool if you have a website and an app and you want the website to be able to communicate with your app.
But it also means that if you have a generic browser that can load any website, you don't want to blindly trust messages that are posted by these websites.
So make sure you do the right message validation and then check that the object is of the expected types and so forth.
So now I'd like to ask Beth to come up on stage again and show off WKPedia for iPad.
[ Applause ]
So I'll show you WKPedia for iPad and then we'll add some user scripts and some script messaging to add these cool new features.
Okay, so back into Xcode, so I'm using the same Xcode project for both versions of WKPedia actually.
So I'm just going to close up the OS X version, open iOS code, and switch my scheme to the iOS scheme, so that now when I build and run, I'll be building and running for the iPad simulator.
All right, so let me show you what we have.
All right, here's WKPedia for the iPad.
So again, our idea that we want to use user scripts for is we want to hide some of this content, we're going to hide this side bar so that we can maximize the screen space on this smaller screen, and we also want to hide this table of contents section.
And then we want to extract all of the data from the table of contents section and put it into a UI table view that we already have in place.
So we can do this with two user scripts.
First we will add a user script to hide the things on the page that we want hidden.
And then we'll go back and fill in our UI table view with another one.
So I have one here called hide dot js.
First I'm creating a style element and I'm appending it into the document.
And the style element, I'm giving it just three styles.
So we used the Safari Web Inspector to look at what the Wikipedia code looks like right now, and we saw that the table of contents that we want to hide has a class name of TOC.
So I'm adding another style to that class.
I'm setting it to display none, and I'm making that important so that it will override any other display styles they have set on it.
And the side panel has an ID of NW panel, so I'm setting that to display none as well.
And finally, I just want to adjust the margin on the content to make sure it fills in that extra area now that the side panel's gone.
So now we just need to add some code to invoke it.
So WebView controller, this is my main class in my iPad version.
You'll see this is where again we have a property for our WKWebView should look awfully familiar.
So here I have a currently empty method called add user scripts to content controller.
So you can see this is invoked from load view.
So and then it's passing in the WK user content controller, which is a part of the configuration.
So Anders mentioned the configuration earlier on in the talk.
So user scripts are an example of something that's part of the configuration.
So that's an example of why you want your different WKWebViews to share a configuration, so they all get the user scripts.
All right, so let's invoke this script.
It's just three lines of code, so first we're creating an N S string with the contents of URL hide dot js.
So we're going to take that file that I just added to the project and turn it into an N S string.
Then I'm creating a WK user script object with that string.
I'm giving it an injection time of WK script injection time at document start.
So this means this script will be injected and it will run right after the document element is created before anything else happens.
That's good, that's what I want to happen for this script, because I want to make sure those styles are there right away.
I don't want the chance for the table of contents and the side bar to appear and only then does my script run, so then they disappear.
That would be terrible.
So document start, that's what I want.
And yeah, for the mainframe only.
Okay, then finally I just need to add the user script to the user content controller, and that should be all I have to do.
So let's build and run.
And success, the side panel is gone, it's not longer there.
We just have the article taking up the full width of the page.
And we also have hidden the table of contents section, we go right into the history section.
Awesome. So now let's add a second user script to get the data from that table of contents section, put it into our UI table view.
So this goes through to that table of contents section, it bundles up all the data we want, puts it into an array, and then it sends it this is the important part it sends it as a message back to my app.
So back here in my add user scripts to content controller method, I'll add a little bit more code.
Okay, I'm creating another N S string with the contents of URL.
This time it's with my fetch dot js class.
And this one I want to be injected at the document end time.
So this time I want to make sure that the DOM has been built up, that it has all of those elements representing the table of contents in it before my script runs.
Otherwise, it's not going to find anything.
So add script message handler, set it to self, and give it a name, did fetch table of contents.
So what I'm doing in that method is I'm making sure that this is the script that I'm expecting to be calling, it did fetch table of contents.
And if so, I'm calling into my UI table view class with a method that I've written there.
So I'll show that to you.
Then I call make entries, which is implemented right up here.
And I go through that array and I make sure it's all the data that I'm expecting it to be, that I've written my script to send over.
It's a string representing the title.
It's the URL representing the URL that that title would navigate to so that we can have our app navigate to that URL when the user taps on it.
And if I have all of that data like I'm expecting it, I create a table of contents entry and I add it to an array.
And then finally back here this is where I invoked that method, I'm going to tell the table view to reload its data at this point.
And then that data should all be in there, okay.
So set this all up, let's see if it works.
Let's build and run.
So here we are, WKPedia, every thing's hidden still, great.
Contents did not quite work, all right.
I can even inspect the user scripts that I've injected, which is pretty cool.
But don't worry, only you can do this with your app, other people won't be able to inspect your app.
So let me open the inspector.
Let's make sure this happens on all pages.
Go to a new page, still no table of contents, okay.
Here, if I go over into my resources panel, here let me zoom this in for actually.
You can see that I have my user scripts over here under the extra scripts section.
So under user script 2, this is the script I want to look at so I can add a breakpoint there and just reload right in the inspector.
All right, let's get rid of that.
Okay, let's step through to OC links, all right.
Do we get into the loop?
We do. Great, we get the texts, not a text break.
Okay, well, there's the bug, we had 2 breaks in a row so we never got to the code that added any of this data to an array.
So this should be an easy bug to fix now that I've spotted it.
So let's go back into Xcode.
There she is, all right, let's build and run.
Yes, we have our table of contents.
So now I have our table of contents.
If I click around here, I'll navigate to the right section of the document because I was sure to include that data when I passed it back from the script.
And there you have it.
So today we've built a dedicated Wikipedia browser app for Mac and for iOS.
We've added some advanced features to make our app interact with our web content.
And we've done it all with the modern WebKit API.
So that's all I have for you, I'll hand it back to Anders now.
Wow, this is really going to make my Wikipedia browsing so much more efficient on my way to work.
So the Modern WebKit API available for iOS 8 and OS X Yosemite same API on both platforms.
It's multi-process, which is great for responsiveness and also for battery life.
And with user script and script messages, web pages can talk to your app and vice versa.
Now, we've only shown a handful of classes here, but the Modern WebKit API has much more to offer.
So here's what I want you to do, adopt the Modern WebKit API.
If you're thinking of writing a new app, I would strongly consider using the new API.
If you already have an app that is using either UIWebView or WebView, don't worry, those API's are still there.
And since this is a new API, we would really love to hear what you think about it, so buy radars, come to the labs, bring your code, post in the developer forums.
If you want to contact our frameworks evangelism team, you can do so at this email address.
There's more information on WebKit and Safari on the Safari Dev Center.
And the Modern WebKit API is part of the WebKit Open Source Project, so you can go to WebKit.org and check it out.
You can also go to the developer forums and post your questions and we'll make sure to answer them.
Related sessions, if you have an app and a website and you want to make sure they work together, there's a great session this afternoon called Your App, Your Website, and Safari.
On Thursday, there's a session about the web inspector.
And on Friday morning, there's a really interesting session where engineers on the iWork team talk about best practices for sharing code between iOS and OS X.
And thank you very much for coming.