Как мой код iPhone Objective-C может получать уведомления об ошибках Javascript в UIWebView?

Мне нужно, чтобы код iPhone Objective-C улавливал ошибки Javascript в UIWebView. Сюда входят неперехваченные исключения, синтаксические ошибки при загрузке файлов, ссылки на неопределенные переменные и т. д.

Это для среды разработки, поэтому SDK не обязательно должен быть кошерным. Фактически, это действительно нужно только для работы на симуляторе.

Я уже обнаружил, что использовал некоторые скрытые трюки WebKit, например, открывать объекты Obj-C для JS и перехватывать всплывающие окна с предупреждениями, но этот все еще ускользает от меня.

[ПРИМЕЧАНИЕ: после публикации я нашел способ использовать делегат отладки. Есть ли способ снизить накладные расходы с помощью консоли ошибок / веб-инспектора?]

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
46
0
36 096
10

Ответы 10

Я сделал это в прошивке 1.x, но не 2.x. Вот код, который я использовал в 1.x, он, по крайней мере, должен вам помочь.

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

Я где-то нашел это, но, похоже, это не работает для меня в 2.1. Предполагая, что это методы, добавленные в подкласс UIWebView.

Robert Sanders 11.10.2008 02:51

Теперь я нашел один способ использования перехватчиков отладчика скриптов в WebView (обратите внимание, НЕ UIWebView). Сначала мне пришлось создать подкласс UIWebView и добавить такой метод:

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

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

Затем вы должны создать класс YourScriptDebugDelegate, который содержит такие методы:

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

Это, вероятно, сильно повлияет на время выполнения, поскольку делегат отладки может также предоставить методы, которые будут вызываться для входа и выхода из кадра стека, а также для выполнения каждой строки кода.

См. http://www.koders.com/noncode/fid7DE7ECEB052C3531743728D41A233A951C79E0AE.aspx для определения WebScriptDebugDelegate в Objective-C++.

Эти другие методы:

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

Обратите внимание, что все это скрыто в частной структуре, поэтому не пытайтесь поместить это в код, который вы отправляете в App Store, и будьте готовы к некоторым взломам, чтобы заставить его работать.

Я попытался создать подкласс UIWebView и добавить метод webView: windowScriptObjectAvailable, как вы описали, но он так и не был вызван. Это с iphone 3.0 SDK. Это больше не работает или я что-то не так делаю?

pjb3 29.06.2009 20:39

Я пробовал это на iphone, и он отлично работает. Но похоже, что возникают только обработанные исключения. Это может оказаться бесполезным, если необработанные исключения исчезнут.

Prcela 22.11.2010 12:31

Я использую исключение было поднято в делегате и перебираю вызывающий фрейм. но все они имеют функцию Name, которая возвращает значение null. Любая идея?

at0mzk 23.02.2011 12:25

Я создал репортер кошерных ошибок SDK, который включает:

  1. Сообщение об ошибке
  2. Имя файла, в котором возникает ошибка.
  3. Номер строки, в которой происходит ошибка
  4. Стек вызовов JavaScript, включая переданные параметры

Это часть платформы QuickConnectiPhone, доступной по адресу проект sourceForge

Существует даже пример приложения, которое показывает, как отправить сообщение об ошибке на терминал Xcode.

Все, что вам нужно сделать, это окружить ваш код JavaScript, включая определения функций и т. д., С помощью try catch. Должно получиться вот так.

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

Он не очень хорошо работает с ошибками компиляции, но работает со всеми остальными. Даже анонимные функции.

Разработка блог здесь находится здесь и включает ссылки на вики, sourceForge, группу Google и Twitter. Может быть, это поможет вам.

Я использовал отличное решение, предложенное Робертом Сандерсом: Как мой код iPhone Objective-C может получать уведомления об ошибках Javascript в UIWebView?

Этот хук для webkit отлично работает и на iPhone. Вместо стандартного UIWebView я выделил производный MyUIWebView. Мне также нужно было определить скрытые классы внутри MyWebScriptObjectDelegate.h:

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

В ios sdk 4.1 функция:

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

это устарел, и вместо него я использовал функцию:

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

Кроме того, я получаю некоторые раздражающие предупреждения вроде «NSObject может не отвечать -windowScriptObject», потому что интерфейс класса скрыт. Я игнорирую их, и это прекрасно работает.

Прямой путь: поместите этот код поверх вашего контроллера / представления, использующего 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

А затем создайте его так:

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

Использование #ifdef DEBUG гарантирует, что он не войдет в сборку выпуска, но я бы также рекомендовал закомментировать его, когда вы его не используете, поскольку это влияет на производительность. Благодарим Роберта Сандерса и Прселу за оригинальный код.

Также при использовании ARC вам может потребоваться добавить «-fno-objc-arc», чтобы предотвратить некоторые ошибки сборки.

Вы также можете просто создать категорию для UIWebView с помощью метода webView: didClearWindowObject: forFrame: вместо того, чтобы создавать экземпляры разных типов классов.

Jeremy Flores 26.05.2012 21:11

Я получил сообщение об ошибке типа приемника WebScriptCallFrame, например, сообщение является предварительным объявлением. на ios6

vagabond 27.08.2014 10:30

Я создал небольшую симпатичную подкатегорию, которую вы можете добавить в свой проект ... Он основан на решении Роберта Сандерса. Престижность.

Вы можете скачать его здесь:

UIWebView + Отладка

Это должно упростить отладку UIWebView :)

Спасибо. Это было чрезвычайно полезно, так как я пришел из MonoTouch и еще не очень хорошо разбирался в Objective C.

Dan Abramov 17.07.2012 22:34

Многообещающе. Однако мне не удалось открыть URL-адрес отладки в моем браузере.

neoneye 17.08.2012 15:21

Мне нравится это решение. Я просто хочу, чтобы было проще получить трассировку стека исключений. По крайней мере, с этим вы получите функцию и номер строки. (хотя номер строки кажется смещенным на единицу - с нуля?)

mpontillo 01.07.2013 10:28

В некоторых случаях более простым решением может быть просто добавить Firebug Lite на веб-страницу.

Один из способов, который работает во время разработки, если у вас есть Safari v 6+ (я не уверен, какая версия iOS вам нужна), - это использовать инструменты разработки Safari и подключиться к UIWebView через него.

  1. В Safari: включите меню «Разработка» («Настройки»> «Дополнительно»> «Показать меню разработки в строке меню»).
  2. Подключите телефон к компьютеру с помощью кабеля.
  3. Элемент списка
  4. Загрузите приложение (либо через xcode, либо просто запустите его) и перейдите на экран, который вы хотите отладить.
  5. Вернувшись в Safari, откройте меню «Разработка», найдите в этом меню имя своего устройства (мое называется iPhone 5), оно должно быть прямо под User Agent.
  6. Выберите его, и вы должны увидеть раскрывающийся список веб-представлений, которые в настоящее время отображаются в вашем приложении.
  7. Если у вас на экране более одного веб-просмотра, вы можете попытаться отличить их друг от друга, наведя курсор на название приложения в меню разработки. Соответствующий UIWebView станет синим.
  8. Выберите имя приложения, откроется окно разработки, и вы сможете проверить консоль. Вы даже можете запускать через него JS-команды.

Это должен быть принятый ответ, это намного лучшее решение, чем другие :)

NSTJ 27.11.2013 07:10

Я хотел бы добавить, что это также работает в вашем симуляторе iOS. Он будет отображаться как пользовательский агент iOS Simulator.

morksinaanab 26.03.2016 07:29

См. Обработку исключений в iOS7: http://www.bignerdranch.com/blog/javascriptcore-example/

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

Привет, Йоав, добро пожаловать в Stack Overflow. Вы можете найти это полезным: stackoverflow.com/help/how-to-answer. Пожалуйста, подумайте о том, чтобы сделать ваш ответ более полезным для читателей: «Ссылки на внешние ресурсы приветствуются, но, пожалуйста, добавьте контекст вокруг ссылки, чтобы ваши коллеги-пользователи имели некоторое представление о том, что это такое и почему она существует. Всегда цитируйте наиболее релевантную часть важная ссылка на случай, если целевой сайт недоступен или постоянно отключен ». «Краткость приемлема, но более полные объяснения лучше».

Honza Zidek 23.02.2015 01:48

Первая настройка WebViewJavascriptBridge, затем переопределите функцию console.error.

В javascript

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

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

Другие вопросы по теме