Incorporating web code and content into apps is becoming more the norm for app development. With Windows 8.1 Preview, you have choices in how you do that. Developers might be looking to integrate an advertising SDK, collecting web analytics about who’s using their app, or leveraging mature online web apps and services. To support existing code and content from the web, we’ve made more investment in the WebView control for Windows 8.1.
You may remember that the XAML framework included a WebView control, which enables you host web content in your XAML app. After getting feedback from developers who used this control in their Windows 8 apps, we made some improvements for Windows 8.1. To take advantage of these changes, make sure your app manifest is updated to require Windows 8.1.
WebView support in Windows Store apps written in HTML and JavaScript
With Windows 8.1, the WebView control is now available to use in HTML apps. Previously, apps written in HTML and JavaScript needed to use an iframe if they wanted to host web content that’s not part of the app. When you use the WebView control for site hosting you get better isolation, navigation, smart screen, and support for sites that can’t be hosted in an iframe. For more info see the talk John Hazen gave at the //Build conference.
In HTML apps, the WebView control is instantiated with the x-ms-webview element:
<x-ms-webviewid="WebView"src=”http://www.microsoft.com” style=”width:400px; height:400px;”>x-ms-webview>
The same underlying control is used for both the HTML and XAML versions of the control, so they have the same functionality. The only difference is some of the API names and data types are customized to fit in with the semantics of each platform.
WebView z-order layering changes
In Windows 8, the WebView control was implemented as an overlay window over the top of the app’s immersive window. It didn’t participate nicely in the app’s layering, and would sit on top of everything else such as popups, combo boxes, and the app bar. This is one of the main pain points we heard from customers using the control in Windows 8.
In Windows 8.1, however, WebView doesn’t have these layering issues. For example, this screenshot of a test app shows WebView with transforms applied and an app bar displaying over the top.
XAML
<WebViewx:Name="WebView1"RenderTransformOrigin="0.5,0.5"><WebView.RenderTransform><CompositeTransformRotation="30"ScaleX="1"ScaleY="1"SkewX="15"SkewY="0"/>WebView.RenderTransform>WebView>
HTML
<x-ms-webviewid="WebView1"style="width:800px; height:600px; transform: rotate(30deg) skewX(15deg)">x-ms-webview>
The new implementation for Windows 8.1 is now implemented as a DirectComposition surface, instead of an overlay window, and that allows developers to mix XAML and web content:
- Popups show above WebView automatically, without using WebViewBrush
- You can animate WebView and animations are hardware accelerated!
- You can apply render transforms such as opacity, scale, skew, rotate, and translate. Input is translated for all 2D, but not 3D, transforms. In the screenshot above, you can click/tap on the textboxes and buttons inside the transformed WebView.
This new windowless mode is automatic for apps designed for windows 8.1. Windows 8 apps running on Windows 8.1 continue to use windowed mode for compatibility.
Browsing functionality
One of the scenarios for using WebView in apps is to provide a lightweight browsing experience. For example, an RSS feed app might provide a browsing experience for links in the content. WebView now includes additional API surface to make that easier:
- Navigation history is now tracked by the control, and the API includes:
- GoBack() and GoForward() methods to move through the history
- CanGoBack and CanGoForward properties to indicate if the navigation history supports those operations
- Refresh() method
- Stop() method
The event lifecycle for navigation has been updated and extended. The main flow is now:
- NavigationStarting– Indicates the WebView is starting a navigation due to an API call, user interaction, script etc. To enable filtering of the navigation, the eventArgs includes a cancel property which, when set to true, cancel the navigation.
- ContentLoading– Indicates the HTML content has been received and is being loaded into the control.
- DOMContentLoaded– Indicates that the main page has finished loading. If you need to use InvokeScriptAsync, wait for this event.
- NavigationCompleted– Indicates the navigation is complete, or that a failure has occurred and why.
The exceptions to this flow are:
- UnviewableContentIdentified– Is fired when a user navigates to content other than a webpage. The WebView control is only capable of displaying HTML content. It doesn’t support displaying standalone images, downloading files, viewing Office documents, etc. This event is fired so the app can decide how to handle the situation. Windows.System.Launcher.LaunchFileAsync() can be called so that the shell determines the right app to handle the file.
- UnsafeContentWarningDisplaying– Is fired when the SmartScreen filter has detected that the page may be unsafe, and the SmartScreen shield is shown. SmartScreen detection is asynchronous, so can occur during and after navigation. When this event is fired, the page content can no longer be interacted with using InvokeScriptAsync etc.
These events are demonstrated in the first scenario of the WebView sample.
As webpages can contain iframes, and each iframe has its own navigation sequence, WebView now includes a set of parallel events for detecting the lifecycle of iframes within the page. These events are:
They follow the same pattern as the main page navigation events.
Content sources
In Windows 8, you could use WebView to load content from:
- The internet using the Navigate() method and Source property (src in HTML)
- Content from the app package, using “ms-appx-web:///directory/file” protocol as the Uri with the above APIs
- The app can programmatically supply HTML markup using the NavigateToString() method
To improve scenarios where WebView is used to host embedded web content, we have added two content sources – Local State directories and the StreamUriResolver.
Local state directories
Sometimes you might want to create and/or store content locally, but then also load the content into WebView. For example, if you have an HTML game with downloadable levels, you can store the levels in the local state directories so they are available offline.
WebView can now load content from the local and temporary app state folders. These are the same folders that are discoverable using the LocalFolder and TemporaryFolder APIs, and can be used for storing app content. The app can create directories and files under these folders, and then navigate to them using the ms-appdata protocol.
To enable an app to have multiple islands of content that are isolated from each other, you are required to create a sub directory under the local or temporary folder’s and store the content within that directory. The Uri scheme is:
- "ms-appdata:///local/TopLevelDirectory/file" for files from the local state and
- "ms-appdata:///temp/TopLevelDirectory/file" for files from the app’s temporary state folder
Each of the top level directories under the local or temp folder is isolated from each other, and browsing to a folder is not enabled unless explicitly navigated to using the navigate API. For example, an eBook app might have a set of different books that it wants to store locally. It can create a folder per book, and then navigate to a book. If each book has its own folder under the LocalFolder, each book would be isolated from each other and cannot access content from other book’s folders.
The following code will create a “NavigateToState” folder under the local state folder and then copy a file from the app package to it.
C#
async void createHtmlFileInLocalState() { StorageFolder stateFolder = await ApplicationData.Current.LocalFolder.CreateFolderAsync("NavigateToState", CreationCollisionOption.OpenIfExists); StorageFile htmlFile = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync("html\\html_example2.html"); await htmlFile.CopyAsync(stateFolder, "test.html", NameCollisionOption.ReplaceExisting); loadFromLocalState.IsEnabled = true; }
JavaScript
function createFileInState() { Windows.Storage.ApplicationData.current.localFolder.createFolderAsync("NavigateToState", Windows.Storage.CreationCollisionOption.openIfExists).then(function (stateFolder) { Windows.ApplicationModel.Package.current.installedLocation.getFileAsync("html\\simple_example.html").then(function (htmlFile) {return htmlFile.copyAsync(stateFolder, "simple_example.html", Windows.Storage.CreationCollisionOption.failIfExists); }); }); }
This can then be navigated to in the WebView using:
C#
string url = "ms-appdata:///local/NavigateToState/test.html"; WebView1.Navigate(new Uri(url));
JavaScript
function navigateToState() { document.getElementById("webview").navigate("ms-appdata:///local/NavigateToState/simple_example.html"); }
Custom URI resolving
The NavigateToString method provides an easy way to navigate to a single HTML document. However it’s common for webpages to reference other content such as css, scripts, images, and fonts that aren’t handled in this way. To solve this problem in Windows 8.1, we’ve added a new capability to WebView to use an UriToStreamResolver object to provide custom URI resolution.
This enables the host app to intercept requests under a specific URI pattern and supply the content as a stream. It can supply the content programmatically for the resources for a webpage, or series of pages, without needing to persist the content to the file system first. A scenario where this may be useful is to present encrypted content – the content can be persisted on disk as encrypted files, or package such as a cab, and then when content is requested it can be decrypted on the fly and supplied to the WebView.
To use this capability, you create an UriToStreamResolver object, and pass it to WebView using the NavigateToLocalStreamUri() method. The UriToStreamResolver object is a WinRT object that needs to implement the IUriToStreamResolver interface, which has one method:
IAsyncOperationUriToStreamAsync(Uri uri)
This method takes the URI and returns the stream. The URI is in the form of “ms-local-stream://appname_KEY/folder/file”.
For example, the following code shows a resolver that serves files from the app package.
////// Sample URI resolver object for use with NavigateToLocalStreamUri/// This sample uses the local storage of the package as an example of how to write a resolver./// publicsealedclass StreamUriWinRTResolver : IUriToStreamResolver {/// /// The entry point for resolving a Uri to a stream./// /// /// public IAsyncOperation UriToStreamAsync(Uri uri) {if (uri == null) {thrownew Exception(); }string path = uri.AbsolutePath;// Because of the signature of this method, it can't use await, so we // call into a separate helper method that can use the C# await pattern.return getContent(path).AsAsyncOperation(); }/// /// Helper that maps the path to package content and resolves the Uri/// Uses the C# await pattern to coordinate async operations/// private async Task getContent(string path) {// We use a package folder as the source, but the same principle should apply// when supplying content from other locationstry { Uri localUri= new Uri("ms-appx:///html" + path); StorageFile f = await StorageFile.GetFileFromApplicationUriAsync(localUri); IRandomAccessStream stream = await f.OpenAsync(FileAccessMode.Read);return stream.GetInputStreamAt(0); }catch (Exception) { thrownew Exception("Invalid path"); } } }
Multiple resolvers can be created, and handed to WebView, so to map the resolver instance to the URI, it uses a key as part of the hostname field, which is why the uri format is “ms-local-stream://appname_KEY/folder/file”. The best way to create this URI is to use the BuildLocalStreamUri() method passing a key string and a relative URI for the first page. When you call NavigateToStreamURI, you pass the URI and the resolver object, and that instance of the resolver will be associated with the key of the URI.
The resolver will be remembered for the lifetime of the WebView instance. Until the instance of WebView is destroyed, or the key is associated with a new resolver object, it continues to be used to resolve URIs with that key.
In JavaScript apps, it is not possible to code an UriResolver object in JavaScript. However, you can use a resolver that is written in C++, C# or VB. For more details, see Scenario 4 in the WebView SDK Sample.
Capturing a snapshot of the WebView as a bitmap
One of the common requests we’ve had for WebView is the ability to capture a snapshot of the WebView as a Bitmap, which can then be manipulated as required by the app. This can now be done using the CapturePreviewToStreamAsync() or capturePreviewToBlobAsync() methods depending on the platform.
For example, the following code creates a file in the local state folder and save the bitmap of the WebView into the file.
C#
private async void Btn_Click(object sender, RoutedEventArgs e) { StorageFile f = await ApplicationData.Current.LocalFolder.CreateFileAsync("test.png", CreationCollisionOption.ReplaceExisting); IRandomAccessStream output = await f.OpenAsync(FileAccessMode.ReadWrite); await WebView1.CapturePreviewToStreamAsync(output); await output.FlushAsync(); output.Dispose(); }
JavaScript
function captureBitmap() {var webviewControl = document.getElementById("webview"); Windows.Storage.ApplicationData.current.localFolder.createFileAsync("test.png", Windows.Storage.CreationCollisionOption.replaceExisting).then(function (file) { file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (stream) {var captureOperation = webviewControl.capturePreviewToBlobAsync(); captureOperation.oncomplete = function (completeEvent) {var inputStream = completeEvent.target.result.msDetachStream(); Windows.Storage.Streams.RandomAccessStream.copyAsync(inputStream, stream).then(function () { stream.flushAsync().done(function () { inputStream.close(); stream.close(); }); }); }; captureOperation.start(); }); }); }
The data can also be captured to an in memory stream and manipulated in memory, for example scaling it using the BitMapTransform object. When capturing a snapshot, the captured image is bounded by the area of the content within the control’s viewport.
Script notify changes in XAML
To improve the security of WebView, we’ve restricted when window.external.notify() can be used from WebView content. These restrictions prevent untrusted content, or content that has been tampered with, from sending messages that are executed without validation to the host. For content to be able to send notifications the following conditions apply:
- The source of the page should be from the local system via NavigateToString(), NavigateToStream() or ms-appx-web:///
Or
- The source of the page is delivered via https:// and the site domain name is listed in the app content URI’s section of the package manifest.
As part of this change, AllowedScriptNotifyUris and AnyScriptNotifyUri are deprecated and will return errors in Windows 8.1 apps.
XAML API changes and deprecation
As the XAML and HTML versions of the WebView are based around the same core component, we wanted to move the APIs to be consistent between the controls. This has resulted in a few API changes on the XAML side:
- InvokeScript() and DataTransferPackage are no longer supported. They are replaced by InvokeScriptAsync() and CaptureSelectedContentToDataPackageAsync() methods.
- AllowedScriptNotifyUris and AnyScriptNotifyUri are no longer supported. They are replaced by the INotifyEventArgs’ CallingUri property that allows the app to get the URI of the page that fired ScriptNotify.
- The LoadCompleted and NavigationFailed events have been replaced by NavigationCompleted.
In XAML these APIs are no longer supported, which means that Windows 8.1 apps can still be built using the API, but will receive compiler warnings.
In closing
We have made significant improvements to the WebView control for XAML and have fixed some of the main pain points that developers have felt in Windows 8. Plus as an HTML and JavaScript developer, you can now use WebView in your apps as well. We hope you’ll take time to try out the new version of the control and provide feedback on the XAML and HTML forums. We look forward to seeing the apps you can create using it.
-Sam Spencer, Senior Program Manager, Windows