This is a second part of my small series of blogposts about unlocking hidden features of Windows 10 UWP WebView. You can read the first part about Displaying HTTPS page with invalid certificate in UWP WebView here.
When using WebView in your Windows Store app you often need to communicate between your C# app and the code inside the WebView. For executing JavaScript code in WebView from C# it’s easy to use the WebView.InvokeScriptAsync method. This asynchronous action executes the specified script function from the currently loaded HTML with specific arguments. This method works on any page loaded in WebView.
But the problem arises when you want the JavaScript code in WebView to notify your C# code. By default the JavaScript should call the window.external.notify method with string parameter and this call then raises ScriptNotify event on the WebView. This call works fine when used on pages loaded from application resources, when using NavigateToString or when using NavigateToLocalStreamUri.
The problem
Unfortunately window.external.notify does not work when used in web pages loaded from Local storage using ms-appdata protocol. According to Microsoft this is by design “This was a policy decision we made”. I have no idea why this decision was made since it has no effect in terms of security, only makes development harder if I want to use ms-appdata Uri as WebView Source.
Lastly window.external.notify can be used in pages loaded from the Internet using standard HTTPS protocol, BUT it’s necessary to explicitly whitelist target domains in package.appxmanifest
In this whitelist editor it’s not possible to use HTTP addresses at all or generic wild-card to “enable all HTTPS websites”.
You can imagine that this is quite a big limitation. Let’s say you want to create your own browser that will be injecting JavaScript into all pages to find the used favicon – well, that’s not possible using the window.external.notify event.
The solution
Luckily there is a solution when developing apps for Windows 10 UWP: the WebView.AddWebAllowedObject method.
In Windows 10 it’s possible to create native class in C#, VB or C++ that can be passed to the WebView object and which behaves like a native JavaScript object. This class must be created in another Project of type Windows Runtime Component and marked with [AllowForWeb] attribute. JavaScript code then can call any method on this object and the actual code of this method runs in the C#/VB/C++ implementation.
//KeyHandler.cs [AllowForWeb] public sealed class KeyHandler { public void setKeyCombination(int keyPress) { Debug.WriteLine("Called from WebView! {0}", keyPress); } } // MainPage.xaml.cs private void Web_OnNavigationStarting(WebView sender, WebViewNavigationStartingEventArgs args) { // WebView native object must be inserted in the OnNavigationStarting event handler KeyHandler winRTObject = new KeyHandler(); // Expose the native WinRT object on the page's global object Web.AddWebAllowedObject("NotifyApp", winRTObject); } private async void Web_OnDOMContentLoaded(WebView sender, WebViewDOMContentLoadedEventArgs args) { try { // inject event handler to arbitrary page once the DOM is loaded // in this case add event handlet to click on the main <table> element await Web.InvokeScriptAsync("eval", new[] { "document.getElementById(\"hnmain\").addEventListener(\"click\", function () { NotifyApp.setKeyCombination(43); }); "}); } catch (Exception e) { Debug.WriteLine(e); } } |
And the best part? This native object can be passed and used in WebView with any page, completely ignoring the Content URIs domain whitelist in Package.appxmanifest.
This is a really good news for developers since they are no longer limited by artificial rules in the app manifest. But on the other hand it’s still necessary to carefully evaluate all calls from the native object. Keep in mind that any code on the page can access the native object as well and send malicious values to the app .
I’ve created simple sample where I load into my WebView the Hacker News web, inject KeyHandler native object and attach event handler when clicking the hnmain element that calls back method on the KeyHandler object back to the C# app.
Summary
It’s possible to use notification between C# and JavaScript code in both ways on any page loaded into WebView using the WebView.AddWebAllowedObject method. It’s quite easy to use it but keep in mind that with great power comes great responsibility.
Pingback: Dew Drop – January 15, 2016 (#2168) | Morning Dew
Thank you, this helped me greatly in a WebView-intensive project.
How can I use ScriptNotify in this case?
Is it creating custom notify event on the shared WinRT class? Wont the webview ScriptNotify work?
Is it not possible to pass a non-primitive typed argument from the InvokeScriptAsync pass to JavaScript into the sealed class illustrated? Even well behaved types as Windows::Foundation::Collections::ValueSet appear to be not well liked on both sides.
Great code. One note, watch out, if you have Script debugging on for the webview via UWP enabled. If you do this sample will not work. To enable Script Debugging for WebView : VS2017->Debug->Application Process->Change from ‘Managed Only’ to ‘Script’