¿Cómo se puede notificar a mi iPhone código Objective-C de los errores de Javascript en un UIWebView?

Necesito que mi código de iPhone Objective-C detecte errores de Javascript en un UIWebView. Eso incluye excepciones no detectadas, errores de syntax al cargar archivos, referencias de variables indefinidas, etc.

Esto es para un entorno de desarrollo, por lo que no necesita ser SDK-kosher. De hecho, solo necesita funcionar en el simulador.

Ya he descubierto que usé algunos de los trucos ocultos de WebKit para, por ejemplo, exponer objetos Obj-C a JS e interceptar ventanas emergentes de alerta, pero este todavía me está eludiendo.

[NOTA: después de publicar esto encontré una forma de usar un delegado de depuración. ¿Hay alguna manera de reducir la sobrecarga usando la consola de error / inspector web?]

Ahora he encontrado una forma de usar los ganchos del depurador de scripts en WebView (nota, NO UIWebView). Primero tuve que subclasificar UIWebView y agregar un método como este:

- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject { // save these goodies windowScriptObject = newWindowScriptObject; privateWebView = webView; if (scriptDebuggingEnabled) { [webView setScriptDebugDelegate:[[YourScriptDebugDelegate alloc] init]]; } } 

A continuación, debe crear una clase YourScriptDebugDelegate que contenga métodos como estos:

 // in YourScriptDebugDelegate - (void)webView:(WebView *)webView didParseSource:(NSString *)source baseLineNumber:(unsigned)lineNumber fromURL:(NSURL *)url sourceId:(int)sid forWebFrame:(WebFrame *)webFrame { NSLog(@"NSDD: called didParseSource: sid=%d, url=%@", sid, url); } // some source failed to parse - (void)webView:(WebView *)webView failedToParseSource:(NSString *)source baseLineNumber:(unsigned)lineNumber fromURL:(NSURL *)url withError:(NSError *)error forWebFrame:(WebFrame *)webFrame { NSLog(@"NSDD: called failedToParseSource: url=%@ line=%d error=%@\nsource=%@", url, lineNumber, error, source); } - (void)webView:(WebView *)webView exceptionWasRaised:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame { NSLog(@"NSDD: exception: sid=%d line=%d function=%@, caller=%@, exception=%@", sid, lineno, [frame functionName], [frame caller], [frame exception]); } 

Probablemente exista un gran impacto en el tiempo de ejecución, ya que el delegado de depuración también puede proporcionar los métodos que se deben invocar para ingresar y salir de un marco de stack, y para ejecutar cada línea de código.

Consulte http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx para la definición Objective-C ++ de WebScriptDebugDelegate.

Esos otros métodos:

 // just entered a stack frame (ie called a function, or started global scope) - (void)webView:(WebView *)webView didEnterCallFrame:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame; // about to execute some code - (void)webView:(WebView *)webView willExecuteStatement:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame; // about to leave a stack frame (ie return from a function) - (void)webView:(WebView *)webView willLeaveCallFrame:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame; 

Tenga en cuenta que todo esto está oculto en un marco privado, así que no intente poner esto en el código que envía a la tienda de aplicaciones, y prepárese para algunos hackers para que funcione.

Creé una pequeña y agradable categoría que puedes agregar a tu proyecto … Se basa en la solución de Robert Sanders. Prestigio.

Puedes descargarlo aquí:

UIWebView + Depuración

Esto debería hacer mucho más fácil depurarlo UIWebView 🙂

Utilicé la gran solución propuesta por Robert Sanders: ¿cómo puede mi código iPhone Objective-C ser notificado de los errores de Javascript en un UIWebView?

Ese gancho para webkit funciona bien también en iPhone . En lugar de UIWebView estándar, asigné MyUIWebView derivado. También necesitaba definir clases ocultas dentro de MyWebScriptObjectDelegate.h:

@class WebView;
@class WebFrame;
@class WebScriptCallFrame;

Dentro del ios sdk 4.1 la función:

 - (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject 

está en desuso y en lugar de usar la función:

 - (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame 

Además, recibo algunas advertencias molestas como “NSObject puede no responder -WindowScriptObject” porque la interfaz de clase está oculta. Los ignoro y funciona bien.

Straight Forward Way: coloque este código en la parte superior de su controlador / vista que está usando UIWebView

 #ifdef DEBUG @interface DebugWebDelegate : NSObject @end @implementation DebugWebDelegate @class WebView; @class WebScriptCallFrame; @class WebFrame; - (void)webView:(WebView *)webView exceptionWasRaised:(WebScriptCallFrame *)frame sourceId:(int)sid line:(int)lineno forWebFrame:(WebFrame *)webFrame { NSLog(@"NSDD: exception: sid=%d line=%d function=%@, caller=%@, exception=%@", sid, lineno, [frame functionName], [frame caller], [frame exception]); } @end @interface DebugWebView : UIWebView id windowScriptObject; id privateWebView; @end @implementation DebugWebView - (void)webView:(id)sender didClearWindowObject:(id)windowObject forFrame:(WebFrame*)frame { [sender setScriptDebugDelegate:[[DebugWebDelegate alloc] init]]; } @end #endif 

Y luego instanciarlo así:

 #ifdef DEBUG myWebview = [[DebugWebView alloc] initWithFrame:frame]; #else myWebview = [[UIWebView alloc] initWithFrame:frame]; #endif 

Usar #ifdef DEBUG garantiza que no se incluya en la versión de lanzamiento, pero también recomendaría comentarlo cuando no lo estés usando, ya que tiene un impacto en el rendimiento. El crédito es para Robert Sanders y Prcela por el código original

Además, si usa ARC, puede que necesite agregar “-fno-objc-arc” para evitar algunos errores de comstackción.

Una forma que funciona durante el desarrollo si tienes Safari v 6+ (no estoy seguro de qué versión de iOS necesitas) es usar las herramientas de desarrollo de Safari y enganchar en UIWebView a través de él.

  1. En Safari: habilite el menú Desarrollar (Preferencias> Avanzado> Mostrar el menú Desarrollo en la barra de menús)
  2. Conecte su teléfono a la computadora a través del cable.
  3. Artículo de lista
  4. Cargue la aplicación (ya sea a través de xcode o simplemente ábrala) y vaya a la pantalla que desea depurar.
  5. De vuelta en Safari, abra el menú Desarrollo, busque el nombre de su dispositivo en ese menú (el mío se llama iPhone 5), debe estar justo debajo de User Agent.
  6. Selecciónelo y verá un menú desplegable de las vistas web actualmente visibles en su aplicación.
  7. Si tiene más de una vista web en la pantalla, puede tratar de diferenciarlas al pasar el nombre de la aplicación en el menú de desarrollo. El UIWebView correspondiente se pondrá azul.
  8. Seleccione el nombre de la aplicación, se abre la ventana de desarrollo y puede inspeccionar la consola. Incluso puedes emitir comandos JS a través de él.

Creé un reportero de errores kosher de SDK que incluye:

  1. El mensaje de error
  2. El nombre del archivo en el que ocurre el error
  3. El número de línea en el que ocurre el error
  4. La stack de llamadas de JavaScript, incluidos los parámetros pasados

Es parte del marco QuickConnectiPhone disponible del proyecto sourceForge

Incluso hay una aplicación de ejemplo que muestra cómo enviar un mensaje de error a la terminal de Xcode.

Todo lo que necesita hacer es rodear su código JavaScript, incluidas las definiciones de funciones, etc. con try catch. Debe tener un aspecto como este.

 try{ //put your code here } catch(err){ logError(err); } 

No funciona muy bien con errores de comstackción, pero funciona con todos los demás. Incluso funciones anónimas.

El blog de desarrollo está aquí y contiene enlaces a la wiki, sourceForge, el grupo de google y Twitter. Tal vez esto te ayude.

He hecho esto en el firmware 1.x pero no en el 2.x. Este es el código que utilicé en 1.x, al menos debería ayudarte en tu camino.

 // Dismiss Javascript alerts and telephone confirms /*- (void)alertSheet:(UIAlertSheet*)sheet buttonClicked:(int)button { if (button == 1) { [sheet setContext: nil]; } [sheet dismiss]; }*/ // Javascript errors and logs - (void) webView: (WebView*)webView addMessageToConsole: (NSDictionary*)dictionary { NSLog(@"Javascript log: %@", dictionary); } // Javascript alerts - (void) webView: (WebView*)webView runJavaScriptAlertPanelWithMessage: (NSString*) message initiatedByFrame: (WebFrame*) frame { NSLog(@"Javascript Alert: %@", message); UIAlertSheet *alertSheet = [[UIAlertSheet alloc] init]; [alertSheet setTitle: @"Javascript Alert"]; [alertSheet addButtonWithTitle: @"OK"]; [alertSheet setBodyText:message]; [alertSheet setDelegate: self]; [alertSheet setContext: self]; [alertSheet popupAlertAnimated:YES]; } 

Consulte el manejo de excepciones en iOS7: http://www.bignerdranch.com/blog/javascriptcore-example/

 [context setExceptionHandler:^(JSContext *context, JSValue *value) { NSLog(@"%@", value); }]; 

Primero configure WebViewJavascriptBridge , luego anule la función console.error.

En javascript

  window.originConsoleError = console.error; console.error = (msg) => { window.originConsoleError(msg); bridge.callHandler("sendConsoleLogToNative", { action:action, message:message }, null) }; 

En Objective-C

 [self.bridge registerHandler:@"sendConsoleLogToNative" handler:^(id data, WVJBResponseCallback responseCallback) { NSString *action = data[@"action"]; NSString *msg = data[@"message"]; if (isStringValid(action)){ if ([@"console.error" isEqualToString:action]){ NSLog(@"JS error :%@",msg); } } }]; 

Una solución más simple para algunos casos podría ser simplemente agregar Firebug Lite a la página web.