Windows Phone 8.1: WebView – ScriptNotify & InvokeScriptAsync


En WinRT el control WebView proporciona dos métodos el cual hace que le suba el poder casi hasta que las páginas Web podrían interactuar directamente con la aplicación, proporcionando así una visión totalmente distinta de que este control podría comportarse como una especie de IFRAME o un sandbox dentro de nuestra aplicación.

Este detalle es maravilloso pero así mismo puede ser un gran dolor de cabeza ya que podría ser una puerta trasera (backdoor) de nuestra aplicación y así podríamos afectar completamente la seguridad ya que estamos dejando un portal abierto hacia la Web.

“Puesto que este evento se activa mediante código externo, debe tener cuidado sobre lo que se coloca en el controlador de eventos. Para evitar que los scripts malintencionados aprovechen este evento, asegúrese de habilitarlo solo para los URI de confianza” – MSDN

Teniendo en cuenta estas recomendaciones y tomando las medidas de seguridad necesarias procederemos a implementar estos poderosos métodos.

InvokeScriptAsync

Este método como su nombre lo indica es capaz de ejecutar un script declarado en el fuente destino, por lo general sería una función de JavaScript; adicional puede retornar un mensaje tipo string de modo callback.

En C#

await myWebView.InvokeScriptAsync("scriptName", new string[] { "Parameter1", "Parameter2" });

En JS

function scriptName(Param1, Param2) {
    // ..
}

De esta manera ejecutaremos esa función declarada de manera sencilla.

ScriptNotify

Este evento es capaz de capturar cualquier llamado externo desde el WebView, de manera personal recomiendo devolver datos formateados tipo JSON y en Code Behind deserializarlo de manera segura, sin embargo el alcance de este artículo será devolver datos simples, en otro artículo expondré cómo deserializar en WinRT.

En JS:

window.external.notify(data);

En C#:

public WebView3()
{
        myWebView.ScriptNotify += MyWebView_ScriptNotify;
}

private async void MyWebView_ScriptNotify(object sender, NotifyEventArgs e)
{
        //Data
        e.Value;
}

Esto es, si y solo si, el archivo HTML está embebido como demostró en un post anterior. Si este sitio está publicado se debe hacer lo siguiente:

“Para permitir que una página web externa genere el evento ScriptNotify al llamar a window.external.notify, debe incluir el URI de la página en la sección ApplicationContentUriRules del manifiesto de la aplicación. (Puede hacerlo en Visual Studio en la pestaña URI de contenido del diseñador Package.appxmanifest). Los URI de esta lista deben emplear HTTPS y pueden contener comodines de subdominio (por ejemplo, “https://*.microsoft.com “), pero no pueden contener comodines de dominio (por ejemplo, “https://*.com ” y “https://*.* “). El requisito de manifiesto no se aplica al contenido que procede del paquete de la aplicación, utiliza un URI ms-local-stream:// o se carga mediante NavigateToString.

Nota  Si tiene más de un subdominio, debe utilizar un comodín para cada uno de ellos. Por ejemplo, “https://*.microsoft.com ” coincide con “https://any.microsoft.com” pero no con “https://this.any.microsoft.com. “

Estos cambios no afectan a las aplicaciones compiladas para Windows 8, incluso cuando se ejecutan en Windows 8.1.” – MSDN

Un ejemplo de cómo interactuan se puede ver de la siguiente manera:

Se desea una aplicación que valide el ingreso del usuario mediante su edad

scripts.html

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title></title>
    <style>
        @font-face {
            font-family: 'Segoe UI';
            src: url(segoeuil.ttf);
        }
        html{
            font-family:'Segoe UI', Arial, sans-serif;
        }
        h1{
            color: #0050EF;
        }
    </style>
    <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
</head>
<body>
    <h1>Scripts</h1>
    <p>Write your age: </p>
    <input type="number" id="ageInput" name="ageInput" value="" min="18" max="120" placeholder="> 18" />
    <input type="button" id="send2App" name="send2App" value="Verify passport" />
    <br />
    <p></p>
    <script>
        /// Function used for showing the text returned from app
        function ShowResult(data) {
            $("p:eq(1)").text(data);
        }

        /// Event Handler used for click event in Button
        $("#send2App").on("click", function () {
            var age = $("#ageInput").val();
            window.external.notify(age);
        });
    </script>
</body>
</html>

WebView3.xaml.cs

        public WebView3()
        {
            this.InitializeComponent();

            this.Loaded += WebView3_Loaded;
            myWebView.ScriptNotify += MyWebView_ScriptNotify;
        }

        private async void MyWebView_ScriptNotify(object sender, NotifyEventArgs e)
        {
            int num = 0;

            if (Int32.TryParse(e.Value, out num))
            {
                if (num >= 18)
                {
                    await myWebView.InvokeScriptAsync("ShowResult", new string[] { "You can access to the Control Panel" });
                }
                else
                {
                    await myWebView.InvokeScriptAsync("ShowResult", new string[] { "You can't access to the Control Panel" });
                }
            }
        }

        private void WebView3_Loaded(object sender, RoutedEventArgs e)
        {
            string url = "ms-appx-web:///Experiments/WebView/www/scripts.html";
            myWebView.Navigate(new Uri(url));
        }

WebView3.xaml

...
<WebView x:Name="myWebView" />
...

El código fuente de los ejemplos está en el repositiorio https://github.com/thEpisode/WindowsExperiments