¿Cómo interceptar el evento onbeforeunload en un control WebBrowser?

Tengo una aplicación WinForms en la que he alojado una página web dentro de un control WebBrowser .

El contenido de la página web es el siguiente:

   onbeforeunload test    Test  window.onbeforeunload = function () { return 'Are you sure you want to leave this page?'; };    

Como puede ver, me he suscrito al evento onbeforeunload que permite mostrar un cuadro de diálogo de confirmación antes de navegar fuera de esta página. Esto funciona bien cuando hago clic en el ancla que recarga la página. Se muestra el cuadro de confirmación y el usuario puede cancelar la recarga de la página. Esto funciona bien dentro del control alojado de WinForms.

Ahora, con lo que estoy teniendo dificultades es interceptar y ejecutar este evento cuando el usuario cierra la aplicación WinForms (haciendo clic en el botón X por ejemplo).

Soy capaz de obtener el contenido de esta función en la aplicación WinForms, pero no importa lo que intenté, no pude obtener el contenido de la cadena que esta función devuelve para poder usarla más adelante para falsificar un MessageBox cuando el usuario Intenta cerrar la aplicación:

 webBrowser1.Navigated += (sender, e) => { webBrowser1.Document.Window.Load += (s, ee) => { // In order to get the IHTMLWindow2 interface I have referenced // the Microsoft HTML Object Library (MSHTML) COM control var window = (IHTMLWindow2)webBrowser1.Document.Window.DomWindow; // the bu variable contains the script of the onbeforeunload event var bu = window.onbeforeunload(); // How to get the string that is returned by this function // so that I can subscribe to the Close event of the WinForms application // and show a fake MessageBox with the same text? }; }; webBrowser1.Navigate("file:///c:/index.htm"); 

He probado el método window.execScript a no disponible:

 // returns null var script = string.Format("({0})();", bu); var result = window.execScript(script, "javascript"); 

También he intentado lo siguiente pero también devolvió nulo:

 var result = window.execScript("(function() { return 'foo'; })();", "javascript"); 

Como último recurso, podría usar un analizador javascript de terceros al que puedo alimentar el cuerpo de esta función y lo ejecutará y me dará el valor de retorno, pero ese sería realmente el último recurso. Me alegraría si hubiera una forma más nativa de hacerlo utilizando la biblioteca MSHTML de Microsoft.


ACTUALIZAR:

Esto ahora se resuelve gracias a la excelente respuesta que proporcionó @Hans. Por alguna razón, no pude hacer que su solución funcionara en mi máquina de prueba (Win7 x64, .NET 4.0 Client Profile, IE9, en-US locale) y siempre recibía hr = -2147024809 después de la llamada IDispatch.Invoke . Así que he modificado la firma IDispatch P / Invoke a lo siguiente (esta firma no requiere una referencia a c:\windows\system32\stdole2.tlb para agregarse al proyecto):

 using System; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("00020400-0000-0000-C000-000000000046")] public interface IDispatch { [PreserveSig] int GetTypeInfoCount(out int Count); [PreserveSig] int GetTypeInfo( [MarshalAs(UnmanagedType.U4)] int iTInfo, [MarshalAs(UnmanagedType.U4)] int lcid, out ITypeInfo typeInfo ); [PreserveSig] int GetIDsOfNames( ref Guid riid, [MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr)] string[] rgsNames, int cNames, int lcid, [MarshalAs(UnmanagedType.LPArray)] int[] rgDispId ); [PreserveSig] int Invoke( int dispIdMember, ref Guid riid, uint lcid, ushort wFlags, ref System.Runtime.InteropServices.ComTypes.DISPPARAMS pDispParams, out object pVarResult, ref System.Runtime.InteropServices.ComTypes.EXCEPINFO pExcepInfo, IntPtr[] pArgErr ); } 

y luego me suscribí al evento de cierre del formulario y pude recuperar el mensaje devuelto por el evento onbeforeunload y preguntar al usuario:

 protected override void OnFormClosing(FormClosingEventArgs e) { var window = (IHTMLWindow2)webBrowser1.Document.Window.DomWindow; var args = new System.Runtime.InteropServices.ComTypes.DISPPARAMS(); var result = new object(); var except = new System.Runtime.InteropServices.ComTypes.EXCEPINFO(); var idisp = window.onbeforeunload as IDispatch; if (idisp != null) { var iid = Guid.Empty; var lcid = (uint)CultureInfo.CurrentCulture.LCID; int hr = idisp.Invoke(0, ref iid, lcid, 1, ref args, out result, ref except, null); if (hr == 0) { var msgBox = MessageBox.Show( this, (string)result, "Confirm", MessageBoxButtons.OKCancel ); e.Cancel = msgBox == DialogResult.Cancel; } } base.OnFormClosing(e); } 

IHtmlWindow2.onbeforeunload no muestra el cuadro de diálogo. Simplemente devuelve una cadena que el host debe usar en un cuadro de mensaje. Ya que su aplicación Winforms es el host, debe usar MessageBox.Show (). Llamar antes de descargar es difícil, es un puntero IDispatch cuyo miembro predeterminado (dispid 0) devuelve la cadena. Agregue una referencia a c:\windows\system32\stdole2.tlb y pegue este código:

 using System.Runtime.InteropServices; ... [ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("00020400-0000-0000-C000-000000000046")] public interface IDispatch { int dummy1(); int dummy2(); int dummy3(); [PreserveSig] int Invoke(int dispIdMember, ref Guid riid, int lcid, int dwFlags, [In, Out] stdole.DISPPARAMS pDispParams, [Out, MarshalAs(UnmanagedType.LPArray)] object[] pVarResult, [In, Out] stdole.EXCEPINFO pExcepInfo, [Out, MarshalAs(UnmanagedType.LPArray)] IntPtr[] pArgErr); } 

Lo usarás así:

  protected override void OnFormClosing(FormClosingEventArgs e) { var window = (IHTMLWindow2)webBrowser1.Document.Window.DomWindow; var args = new stdole.DISPPARAMS(); var result = new object[1]; var except = new stdole.EXCEPINFO(); var idisp = (IDispatch)window.onbeforeunload; var iid = Guid.Empty; int hr = idisp.Invoke(0, ref iid, 1033, 1, args, result, except, null); if (hr == 0) { if (MessageBox.Show(this, (string)result[0], "Confirm", MessageBoxButtons.OKCancel) == DialogResult.Cancel) e.Cancel = true; } base.OnFormClosing(e); } 

Acabo de lidiar con un problema similar. Esta es una respuesta tardía, pero espero que pueda ayudar a alguien. La solución se basa en este artículo de MSKB . También funciona para los casos en que la página web maneja el evento antes de attachEvent través de attachEvent o addEventListener .

 void Form1_FormClosing(object sender, FormClosingEventArgs e) { // this code depends on SHDocVw.dll COM interop assembly, // generate SHDocVw.dll: "tlbimp.exe ieframe.dll", // and add as a reference to the project var activeX = this.webBrowser.ActiveXInstance; object arg1 = Type.Missing; object arg2 = true; ((SHDocVw.WebBrowser)activeX).ExecWB(SHDocVw.OLECMDID.OLECMDID_ONUNLOAD, SHDocVw.OLECMDEXECOPT.OLECMDEXECOPT_DODEFAULT, ref arg1, ref arg2); if (!(bool)arg2) { e.Cancel = true; } } 

El código anterior es para la versión de WinForms del control WebBrowser . Para la versión WPF, ActiveXInstance debe obtener primero a través de la reflexión:

  var activeX = this.WB.GetType().InvokeMember("ActiveXInstance", BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic, null, this.WB, new object[] { }) as SHDocVw.WebBrowser; 

Si está feliz de ejecutar prematuramente el código de evento (que podría ser cualquier cosa), lo siguiente captura la cadena para mí en su Window.Load ;

 Object[] args = { @"(" + bu + ")();" }; string result = webBrowser1.Document.InvokeScript("eval", args).ToString();