2008-10-10 8 views
45

Necesito que el código de mi iPhone Objective-C detecte errores de Javascript en un UIWebView. Eso incluye excepciones no detectadas, errores de sintaxis al cargar archivos, referencias de variables indefinidas, etc.¿Cómo se puede notificar a mi iPhone el código Objective-C de los errores de Javascript en un UIWebView?

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 encontrado utilizado algunos de los trucos de WebKit ocultos a, p. 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 utilizar un delegado de depuración. ¿Hay alguna manera de reducir la sobrecarga, utilizando la consola de error/inspector web?]

Respuesta

0

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]; 
} 
+0

había encontrado que en algún lugar, pero no parece funcionar para mí en el punto 2.1. Suponiendo que esos son métodos agregados a una subclase de UIWebView. –

28

He encontrado una forma de utilizar los enlaces de depurador de scripts en WebView (nota, NO UIWebView). La primera vez que tuve que subclase UIWebView y añadir 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 contiene 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 hay un gran impacto en tiempo de ejecución para esto, como el delegado de depuración también puede proporcionar los métodos que se deben invocar para ingresar y salir de un marco de pila, 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 (i.e. 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 (i.e. return from a function) 
- (void)webView:(WebView *)webView willLeaveCallFrame:(WebScriptCallFrame *)frame 
     sourceId:(int)sid 
      line:(int)lineno 
    forWebFrame:(WebFrame *)webFrame; 

nota que esto es todo escondido en un marco privado, por lo que no trate de poner esto en código que se somete a la App Store, y estar preparados para algunos hackery para hacer que funcione

+1

Intenté subclavar UIWebView y agregar el método webView: windowScriptObjectAvailable como describiste, pero nunca se llama. Esto es con el iPhone 3.0 SDK. ¿Esto ya no funciona o estoy haciendo algo mal? – pjb3

+0

Probé esto en iphone y funciona muy bien. Pero, parece que solo se plantean excepciones manejadas. Puede no ser útil si las excepciones no administradas pasaron. – Prcela

+0

im using exceptionWasRaised en el delegado e iterar sobre la persona que llama al fotograma. pero todos tienen functionName devuelve null. ¿Alguna idea? – at0mzk

4

He creado un reportero de error kosher SDK que incluye:

  1. El mensaje de error
  2. El nombre del archivo que ocurre el error en
  3. El número de línea que pasa el error en
  4. El JavaScript callstack incluyendo los parámetros pasados ​​

Es parte del marco QuickConnectiPhone disponible desde the sourceForge project

Incluso hay una aplicación de ejemplo que muestra cómo enviar un mensaje de error a la terminal 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 compilación, pero funciona con todos los demás. Incluso funciones anónimas.

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

11

que utilizan la gran solución propuesta de Robert Sanders: How can my iPhone Objective-C code get notified of Javascript errors in a UIWebView?

Ese gancho para WebKit funciona bien también en iPhone. En lugar de UIWebView estándar, asigné MyUIWebView derivado. Necesitaba también para definir las clases ocultos en el interior MyWebScriptObjectDelegate.h:

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

Dentro de los SDK de iOS 4.1 la función:

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

es obsoleta y en lugar de ella Utilicé 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.

8

manera directa: Pon este código en la parte superior de su controlador/vista que está utilizando el 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 a continuación, crear una instancia de esta manera:

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

El uso de DEBUG #ifdef asegura que no aparece en la compilación de lanzamiento, pero también recomendaría comentarlo cuando no lo estés utilizando, ya que tiene un impacto en el rendimiento. El crédito va a 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 compilación.

+2

También puede hacer una categoría para UIWebView con el método webView: didClearWindowObject: forFrame: en lugar de tener que instanciar diferentes tipos de clase. –

+0

Recibí un error sobre el tipo de receptor. WebScriptCallFrame, por ejemplo, el mensaje es una declaración directa. en ios6 – vagabond

13

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

Puede dowload aquí:

UIWebView+Debug

Esto debería hacer que sea mucho más fácil de depurar que UIWebView :)

+0

Gracias. Esto ha sido tremendamente útil, ya que vengo de MonoTouch y todavía no me he familiarizado bien con Objective C. –

+0

Esta es [cómo uso su solución en MonoTouch] (http://stackoverflow.com/a/11529165/458193). –

+0

Prometedor. Sin embargo, no pude abrir la URL de depuración en mi navegador. – neoneye

-3

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

7

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

  1. En Safari: Activar el menú Desarrollo (Preferencias> Avanzado> Mostrar menú de barra de menú Desarrollo)
  2. conectar el teléfono a la computadora a través del cable.
  3. Artículo de la 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), debería estar justo debajo de User Agent.
  6. Selecciónelo y debería 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 intentar distinguirlas 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.
+2

Esta debería ser la respuesta aceptada, es una solución mucho mejor que las demás :) – NSTJ

+0

Me gustaría añadir que esto también funciona en su simulador de iOS. Se mostrará como agente de usuario 'iOS Simulator' – morksinaanab

0

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

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

Hola, Yoav, bienvenido a Stack Overflow. Puede encontrar esto útil: http://stackoverflow.com/help/how-to-answer.Considere hacer que su respuesta sea más útil para los lectores: "Se alientan los enlaces a recursos externos, pero agregue contexto alrededor del enlace para que los demás usuarios tengan una idea de qué es y por qué está allí. Siempre cite la parte más relevante de un enlace importante, en caso de que el sitio objetivo no esté disponible o se desconecte permanentemente ". "La brevedad es aceptable, pero las explicaciones más completas son mejores". –

0

Primera configuración WebViewJavascriptBridge, luego anular 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); 
     } 
    } 
}]; 
Cuestiones relacionadas