To close this series, I wanted to share a few additional bits related to webview, but not necessarily related to one another!

First, in the previous post I showed how to wrap some of the webview’s capture methods in a promise. The same thing is easy to do with invokeScriptAsync, where the promise is fulfilled with the value returned from the webview’s script:

functon callWebviewScript(webview, targetFunction, argsIn) {
return new WinJS.Promise(function (cd, ed) {
var op = webview.invokeScriptAsync(targetFunction, argsIn);

        op.oncomplete = function (args) {;
//Return value from the invoked function (always a string) is in args.target.result
cd(args.target.result);
};

op.onerror = function (e) { ed(e); };
op.start();
});
}

This kind of structure helps you remember the necessary call to start at the end.

With invokeScriptAsync, be aware that the result you pass back must be a string, otherwise you’ll get an empty string.

In the callWebviewScript function above, note that targetFunction can be set to “eval” and argsIn to whatever script string you want to evaluate–meaning that the webview doesn’t necessarily have to have a named function inside it. Just remember that the result of that eval must again be a string.

The next little tip is just knowing that webview cache cookies and other content just like the browser, even if you remove the webview from the DOM and let it get garbage collected. There aren’t facilities to clear information that’s cached in the webview, though you might be able to achieve this through the Windows.Web.HttpClient API instead.

Lastly, the webview has a characteristic that if you overlay another element on a webview that has 100% opacity, the webview will go black as it fails “layer candidacy” as it’s called. The same thing happens with an overlay element with 0% opacity. (See http://msdn.microsoft.com/en-us/library/jj680148(v=vs.85).aspx under “Unsupported Scenarios” section for the description of scenarios where it fails layer candidacy.)

If, however, the overlay element’s style has an opacity of anything other than 1 or 0, like 0.9999 or 0.0001, then the webview is happy. It’s an odd behavior, but a good one to know about.


One of the key feature of the webview that really sets it apart from the iframe is the ability to capture its content, something that you simply cannot do with an iframe. There are three ways this can happen.

First is the src attribute. Once the webview has navigated (and fired its MSWebViewNavigationCompleted event), src will contain a URI to the content as the webview sees it. For web content, this will be an http[s] URI, which can be opened in a browser. Local content (loaded from strings or app data files) will start with ms-local-web, which can be rendered into another webview using navigateToLocalStream. Be aware that while navigation is happening prior to MSWebViewNavigationCompleted, the state of the src property is indeterminate; use the uri property in those handlers instead.

Second is the webview’s captureSelectedContentToDataPackageAsync method, which reflects whatever selection the user has made in the webview directly. The fact that a data package is part of this API suggests its primary use: the share contract. From a user’s perspective, any web content you’re displaying in the app is really part of the app. So if they make a selection there and invoke the Share charm, they’ll expect that their selected data is what gets shared, and this method lets you obtain the HTML for that selection. Of course, you can use this anytime you want the selected content—the Share charm is just one of the potential scenarios.

As with invokeScriptAsync, the return value from captureSelectedContentToDataPackage­Async is again a DOM-ish object with a start method that you must call to perform the operation. If you want to wrap this in a promise, you can use a structure like this:

function captureWebviewSelectionAsync(webview) {
//Wrap the capture method in a promise
return new WinJS.Promise(function (cd, ed) {
var op = webview.captureSelectedContentToDataPackageAsync();
op.oncomplete = function (args) { cd(args.target.result); };
op.onerror = function (e) { ed(e); };
op.start();
});
In this case, the result you care about us args.target.result in the oncomplete event, which is a Windows.ApplicationModel.DataTransfer.­DataPackage object as used with the Share charm. Calling its getView method will produce a DataPackageView whose availableFormats object tells you what it contains. You can then use the appropriate get* methods like getHtmlFormatAsync to retrieve the selection data itself. Note that if there is no selection, args.target.result will be null, so you’ll need to guard against that. Here’s some code, for instance, to copy the selection from one webview to another, using the function above:

function captureSelection() {
var source = document.getElementById(“webviewSource”);
var promise = captureWebviewSelectionAsync(source);

//Navigate the output webview to the selection, or show an error
var output = document.getElementById(“webviewOutput”);

promise.then(function (dataPackage) {
if (dataPackage == null) { throw “No selection”; }

var view = dataPackage.getView();
return view.getHtmlFormatAsync();
}).done(function (text) {
output.navigateToString(text);
}, function (e) {
output.navigateToString(“Error: ” + e.message);
});
}

Note that the captured selection is an HTML clipboard format that includes the extra information at the top before the HTML from the webview. If you need to extract just the straight HTML, you’ll need to strip off this prefix text up to <!DOCTYPE html>.

Generally speaking, captureSelectedContentToDataPackageAsync will produce the formats AnsiText, Text, HTML Format, Rich Text Format, and msSourceUrl, but not a bitmap. For this you need to use the third method, capturePreviewToBlobAsync, which again has a start method and complete/error events. The results of this capture (in args.target.result within the complete handler) is a blob object for whatever content is contained within the webview’s display area. Here’s another function to wrap that operation in a promise:

function captureWebviewBitmapAsync(webview) {
return new WinJS.Promise(function (cd, ed) {
var op = webview.capturePreviewToBlobAsync();

op.oncomplete = function (args) {
var ras = Windows.Storage.Streams.RandomAccessStreamReference;
var bitmapStream = ras.createFromStream(args.target.result.msDetachStream());
cd(bitmapStream);
};

op.onerror = function (e) { ed(e); };
op.start();
});
}

The result of the promise generated by this function is a blob, which you can use for a number of purposes. If you want to display it in an img element, you can use URL.createObjectURL on this blob directly. This means you can easily load some chunk of HTML in an offscreen webview (make sure the display style is not “none”) and then capture a blob and display the results in an img. Besides preventing interactivity, you can also animate that image much more efficiently than a full webview, applying 3D CSS transforms, for instance. You can also stream the blob in an upload, save it to a file, and so forth.

For other purposes, like the Share charm, you can call this blob’s msDetachStream method, which conveniently produces exactly what you need to provide to a data package’s setBitmap method. This is demonstrated in scenario 7 of the SDK’s HTML Webview control sample (though the sample neglects to use the datarequested event’s deferral correctly).

In part 3 I’ll share a few other closing bits on webview.


In Windows 8, an app written in JavaScript could host other HTML content–both local (in-package) and remote (http[s])–in an iframe element. In Windows 8.1, an iframe is still completely supported for in-package content (ms-appx-[web] URIs), but only supports secure remote content (https). For general hosting of web content (http), apps should be using the x-ms-webview element.

In this post and the few that follow, I wanted to share a few details about migrating code from iframe to webview. For a broader discussion of hosting web content, refer to Chapter 4 of my free ebook, Programming Windows Store Apps with HTML, CSS, and JavaScript, Second Edition. Also see What’s New in Webview for Windows 8.1 on the Windows App Builder’s blog.

For in-package content using ms-appx-[web] URIs, you can continue to use iframes. If you want to switch to webview for any reason with ms-appx-web (you can’t use webview with ms-appx), then you should be able to make a straight change from <iframe src=”…”> to <x-ms-webview src=”…”>. Then you need to change any uses of postMessage to communicate with the iframe to webview.invokeScriptAsync (to call methods in the webview’s script) and the MSWebViewScriptNotify event (to pick up events raised from within the webview using window.external.notify).

For web content, the story gets a little trickier. If you try to direct an iframe in Windows 8.1 to an http:// address, you’ll get a message like this:

APPHOST9625: Unable to navigate to: ‘http://www.mysite.com/’. An iframe attempted to navigate to a URI that is not included in the ApplicationContentUriRules for this app. Use a x-ms-webview element to view the URI instead, or add the URI to the ApplicationContentUriRules section of the package manifest so that the iframe can navigate to it. (In Visual Studio, add this URI to the Content URIs tab of the Manifest Designer.)

Like it says, an iframe in 8.1 must use https for remote content, and then the app’s manifest must have an entry for that site on its Content URIs tab. So if you can use https:// for that site, then you can continue to host it in an iframe. If not, then, you need to use a webview.

With a webview, however, window.external.notify (to raise events from the webview) will work only when src is set to an https:// (and you have a content URI in the manifest) or ms-appx-web::// URI, but not http://.

That said, the webview element can be loaded from alternate source, which is one of the reasons why we have it. For one, you can also use ms-appdata URIs for a webview, either through its src attribute or its navigate method. This makes it possible to download content from the web or generate it dynamically, then render it within the webview. The caveat, however, is that with ms-appdata URIs, you can use invokeScriptAsync but not window.external.notify.

That said, if you load content into a webview using its navigateToString or navigateToLocalStreamUri methods, then you can also use window.external.notify. This means that if you want to load up downloaded or dynamically-generated content into a webview and have it raise events, then you either need to load the content into a memory string and call navigateToString, or you create a local stream provider (refer to the HTML webview control sample for a demonstration, as well as http://blogs.windows.com/windows/b/appbuilder/archive/2013/07/17/what-s-new-in-webview-in-windows-8-1.aspx).

In part 2 we’ll look at capturing webview content.