46 votes

Comment le code Objective-C de mon iPhone peut-il être notifié des erreurs Javascript dans une UIWebView ?

Je dois faire en sorte que le code Objective-C de mon iPhone détecte les erreurs Javascript dans une UIWebView. Cela inclut les exceptions non capturées, les erreurs de syntaxe lors du chargement des fichiers, les références de variables non définies, etc.

Il s'agit d'un environnement de développement, qui n'a donc pas besoin d'être conforme au SDK. En fait, il ne doit fonctionner que sur le simulateur.

J'ai déjà utilisé certaines des astuces cachées de WebKit pour, par exemple, exposer des objets Obj-C à JS et intercepter des popups d'alerte, mais celle-ci m'échappe encore.

[REMARQUE : après avoir publié cet article, j'ai trouvé une solution utilisant un délégué de débogage. Existe-t-il un moyen moins coûteux d'utiliser la console d'erreur ou l'inspecteur Web ?]

28voto

Robert Sanders Points 380

J'ai maintenant trouvé une solution en utilisant les crochets du débogueur script dans WebView (note, PAS UIWebView). J'ai d'abord dû sous-classer UIWebView et ajouter une méthode comme celle-ci :

- (void)webView:(id)webView windowScriptObjectAvailable:(id)newWindowScriptObject {
    // save these goodies
    windowScriptObject = newWindowScriptObject;
    privateWebView = webView;

    if (scriptDebuggingEnabled) {
        [webView setScriptDebugDelegate:[[YourScriptDebugDelegate alloc] init]];
    }
}

Ensuite, vous devez créer une classe YourScriptDebugDelegate qui contient des méthodes comme celles-ci :

// 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]);
}

Cela a probablement un impact important sur le temps d'exécution, car le délégué de débogage peut également fournir des méthodes à appeler pour entrer et sortir d'un cadre de pile, et pour exécuter chaque ligne de code.

Ver http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx pour la définition Objective-C++ de WebScriptDebugDelegate.

Ces autres méthodes :

// 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;

Notez que tout ceci est caché dans un cadre privé, donc n'essayez pas de mettre ceci dans un code que vous soumettez à l'App Store, et soyez prêt à faire quelques piratages pour le faire fonctionner.

1 votes

J'ai essayé de sous-classer UIWebView et d'ajouter la méthode webView:windowScriptObjectAvailable comme vous l'avez décrit, mais elle n'est jamais appelée. Ceci avec le SDK de l'iphone 3.0. Est-ce que cela ne fonctionne plus ou est-ce que je fais quelque chose de mal ?

0 votes

J'ai essayé sur mon iphone et cela fonctionne très bien. Mais, il semble que seules les exceptions manipulées soient levées. Cela peut ne pas être utile si les exceptions non gérées sont passées.

0 votes

J'utilise exceptionWasRaised dans le délégué et j'itère sur le cadre appelant. Mais tous les noms de fonction renvoient null. Une idée ?

13voto

Pablo Points 133

J'ai créé une petite catégorie sympathique que vous pouvez ajouter à votre projet... Elle est basée sur la solution de Robert Sanders. Bravo.

Vous pouvez le télécharger ici :

UIWebView+Debug

Cela devrait rendre le débogage de votre UIWebView beaucoup plus facile :)

0 votes

Merci. Cela a été extrêmement utile, car je viens de MonoTouch et je n'ai pas encore bien maîtrisé Objective C.

0 votes

0 votes

Prometteur. Cependant, je n'ai pas pu ouvrir l'url de débogage dans mon navigateur.

11voto

Prcela Points 1736

J'ai utilisé l'excellente solution proposée par Robert Sanders : Comment le code Objective-C de mon iPhone peut-il être notifié des erreurs Javascript dans une UIWebView ?

Ce crochet pour webkit fonctionne bien aussi sur iPhone . Au lieu de l'UIWebView standard, j'ai alloué le dérivé MyUIWebView. Je devais aussi définir des classes cachées dans MyWebScriptObjectDelegate.h :

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

Dans le sdk ios 4.1 la fonction :

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

est déprécié et à la place, j'ai utilisé la fonction :

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

De plus, je reçois des avertissements ennuyeux comme "NSObject may not respond -windowScriptObject" parce que l'interface de la classe est cachée. Je les ignore et cela fonctionne bien.

9voto

bobc Points 154

Une méthode qui fonctionne pendant le développement si vous avez Safari v 6+ (je ne suis pas sûr de la version iOS dont vous avez besoin) est d'utiliser les outils de développement de Safari et de se connecter à l'UIWebView par ce biais.

  1. Dans Safari : Activez le menu Développer (Préférences > Avancé > Afficher le menu Développer dans la barre de menu).
  2. Branchez votre téléphone à l'ordinateur via le câble.
  3. Élément de liste
  4. Chargez l'application (via xcode ou lancez-la simplement) et allez à l'écran que vous voulez déboguer.
  5. De retour dans Safari, ouvrez le menu Développer, cherchez le nom de votre appareil dans ce menu (le mien s'appelle iPhone 5), il devrait se trouver juste sous Agent utilisateur.
  6. Sélectionnez-la et vous devriez voir une liste déroulante des vues web actuellement visibles dans votre application.
  7. Si vous avez plus d'une vue Web à l'écran, vous pouvez essayer de les différencier en faisant défiler le nom de l'application dans le menu de développement. L'UIWebView correspondante deviendra bleue.
  8. Sélectionnez le nom de l'application, la fenêtre de développement s'ouvre et vous pouvez inspecter la console. Vous pouvez même y envoyer des commandes JS.

2 votes

Cela devrait être la réponse acceptée, c'est tellement une meilleure solution que les autres :)

0 votes

J'aimerais ajouter que cela fonctionne également dans votre simulateur iOS. Il apparaîtra comme agent utilisateur 'iOS Simulator'.

8voto

psy Points 1522

Méthode directe : Placez ce code en haut de votre contrôleur/vue qui utilise le 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

Et ensuite l'instancier comme ceci :

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

L'utilisation de #ifdef DEBUG permet de s'assurer qu'il ne sera pas intégré dans la version finale, mais je recommande également de le commenter lorsque vous ne l'utilisez pas car il a un impact sur les performances. Le crédit va à Robert Sanders et Prcela pour le code original.

De plus, si vous utilisez ARC, vous devrez peut-être ajouter "-fno-objc-arc" pour éviter certaines erreurs de compilation.

2 votes

Vous pouvez aussi simplement créer une catégorie pour UIWebView avec la méthode webView:didClearWindowObject:forFrame : au lieu de devoir instancier différents types de classes.

0 votes

J'ai reçu une erreur sur le type de récepteur WebScriptCallFrame pour le message d'instance est une déclaration en avant. sur ios6

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X