Loupe

[WinRT] Comment utiliser la WebView pour notifier à partir d’un domaine non sécurisé

En théorie, lorsque l’on utilise une WebView sous Windows 8.1, il est possible d’y injecter du JavaScript à éxécuter à l’aide de la méthode InvokeScript, et ce sans contraintes particulières. En revanche, la communication inverse est sujette à une limitation potentiellement génante. Dans un précédent article, j’expliquais que le domaine de la page de la WebView courante doit être trusté. Cela signifie que ce domaine doit apparaître dans le manifeste de l’application WinRT (sous Windows 8.0, il suffisait de le renseigner dans la propriété AllowedScriptNotifyUris de la WebView, mais cela ne fonctionne plus). Un des principaux problèmes de cette méthode est que l’uri doit être renseignée avec HTTPS (or si l’application Web distante ne l’implémente pas, cela ne peut pas fonctionner). J’expliquais aussi dans cette article que lorsqu’une WebView navigue vers du contenu local ou encore à l’aide d’un IUriToStreamResolver, l’API de notification est disponible. Il est donc possible de streamer les ressources Web distante à afficher via un IUriToStreamResolver et de bypasser cette sécurité pour le moins génante dans certains contexte. Il existe toutefois une solution plus simple à mettre en oeuvre.

Le principe générale consiste à injecter une IFRAME invisible dans le DOM de la page de la WebView, et de modifier sa source. La WebView lève un évènement lorsque l’une des IFRAMEs qu’elle contient change de source. On peut donc se servir de cette évènement pour faire remonter des informations de l’intérieur de la WebView vers l’extérieur, comme si l’on utilisait la méthode window.external.notify.

Pour implémenter ce système, il faut dans un premier temps s’abonner à l’évènement NavigationCompleted et y vérifier si l’URI courante correspond à l’une de celles sur lesquels on souhaite autoriser ce système de communication. Si tel est le cas, on peut alors s’abonner à l’évènement FrameContentLoading et injecter le JavaScript nécessaire pour créer l’iframe.

private async void Webview_NavigationCompleted(WebView sender, WebViewNavigationCompletedEventArgs args)
{
    sender.FrameContentLoading -= Sender_FrameContentLoading;
    if (args.Uri.AbsoluteUri == "http://bing.com")
    {
        sender.FrameContentLoading += Sender_FrameContentLoading;
        await sender.InvokeScriptAsync("eval", new[] { "(function() {"
            + "var iframe = document.createElement('iframe');"
            + "iframe.style.display = 'none';"
            + "document.body.appendChild(iframe);"
            + "window._external = {"
            + " notify: function(value) {"
            + "  iframe.src = 'about:blank?value=' + encodeURIComponent(value);"
            + " }"
            + "};"
            + "})();"});

        // ajoutez ce script pour tester
        await sender.InvokeScriptAsync("eval", new[] { "(function() {"
            + "setTimeout(function() {"
            + " window._external.notify('time');"
            + "}, 2000);"
            + "})();"});
    }
}

Comme on peut le voir, il est possible de créer un objet similaire à window.external (ici window._external) de sorte à fournir une interface plus commode pour remonter des messages. Voici le handler pour l’évènement FrameContentLoading, qui permet donc de traiter les notifications émises depuis la WebView :

private void Sender_FrameContentLoading(WebView sender, WebViewContentLoadingEventArgs args)
{
    if (args.Uri.AbsoluteUri.StartsWith("about:blank"))
    {
        var query = args.Uri.Query
            .Substring(1)
            .Split('&')
            .Select(q => q.Split('='))
            .ToDictionary(q => q[0], q => q.Length > 1 
                ? Uri.UnescapeDataString(q[1]) 
                : null);
        if (query.ContainsKey("value"))
            new MessageDialog(query["value"] ?? "", "notify value").ShowAsync();
    }
}

C’est quand même plus simple qu’avec un IUriToStreamResolver ! En ce qui concerne la sécurité, il faut tout de même garder à l’esprit que les limitations habituelles imposées par Microsoft on surement une raison d’être. Un tel système doit certainement comporter des risques et mieux vaut donc bien vérifier les domaines et URIs sur lesquelles on veut l’implémenter.

Ces billets pourraient aussi vous intéresser

Vous nous direz ?!

Commentaires

comments powered by Disqus