Подтвердить что ты не робот

UIWebView JavaScript потерял ссылку на iOS JSContext пространство имен (объект)

Я работаю над доказательством концепции приложения, которое использует двустороннюю связь между Objective C (iOS 7) и JavaScript с использованием среды WebKit JavaScriptCore. Я, наконец, смог заставить его работать так, как ожидалось, но столкнулся с ситуацией, когда UIWebView теряет ссылку на объект iOS, который я создал через JSContext.

Приложение немного сложное, вот основные сведения:

  • Я запускаю веб-сервер на устройстве iOS (CocoaHTTPServer)
  • UIWebView первоначально загружает удаленный URL-адрес и позже перенаправляется обратно на localhost как часть потока приложения (думаю, OAuth)
  • HTML-страница, на которой размещается приложение (на локальном хосте), имеет JavaScript, который должен разговаривать с моим кодом iOS

Здесь сторона iOS, мой ViewController .h:

#import <UIKit/UIKit.h>
#import <JavaScriptCore/JavaScriptCore.h>

// These methods will be exposed to JS
@protocol DemoJSExports <JSExport>
-(void)jsLog:(NSString*)msg;
@end

@interface Demo : UIViewController <UserInfoJSExports, UIWebViewDelegate>
@property (nonatomic, readwrite, strong) JSContext *js;
@property (strong, nonatomic) IBOutlet UIWebView *webView;
@end

И соответствующие части ViewController .m:

-(void)viewDidLoad {
    [super viewDidLoad];

    // Retrieve and initialize our JS context
    NSLog(@"Initializing JavaScript context");
    self.js = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

    // Provide an object for JS to access our exported methods by
    self.js[@"ios"] = self;

    // Additional UIWebView setup done here...
}

// Allow JavaScript to log to the Xcode console
-(void)jsLog(str) {
    NSLog(@"JavaScript: %@", str);
}

Вот (упрощенный для этого вопроса) сторона HTML/JS:

<html>
<head>
<title>Demo</title>
<script type="text/javascript">
    function setContent(c, noLog){
        with(document){
            open();
            write('<p>' + c + '</p>');
            close();
        }

        // Write content to Xcode console
        noLog || ios.jsLog(c);
    }    
</script>
</head>
<body onload="javascript:setContent('ios is: ' + typeof ios)">
</body>
</html>

Теперь, почти во всех случаях это прекрасно работает, я вижу ios is: object как в UIWebView, так и в консоли Xcode. Очень круто. Но в одном конкретном сценарии, в 100% случаев, это не удается после определенного количества переадресаций в UIWebView, и как только загруженная выше страница загружается, он говорит:

ios: undefined

... и остальная часть логики JS завершается, потому что последующий вызов ios.jsLog в функции setContent приводит к исключению объекта undefined.

Итак, наконец, мой вопрос: что может/может привести к утечке JSContext? Я вырыл "документацию" в файлах JavaScript.h и обнаружил, что единственный способ, которым это должно случиться, - это если больше strong ссылок на JSContext, но в моем случае у меня есть один из моих собственных, так что это не кажется правильным.

Моя единственная другая гипотеза заключается в том, что она связана с тем, как я получаю ссылку JSContext:

 self.js = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

Я знаю, что это не может официально поддерживаться Apple, хотя я нашел хотя бы одного SO'er, который сказал, что у него есть одобренное Apple приложение, которое использовало этот самый метод.

ИЗМЕНИТЬ

Следует отметить, что я внедрил UIWebViewDelegate для проверки JSContext после каждой переадресации в UIWebView:

-(void)webViewDidFinishLoad:(UIWebView *)view{
    // Write to Xcode console via our JSContent - is it still valid?
    [self.js evaluateScript:@"ios.jsLog('Can see JS from obj c');"];
}

Это работает во всех случаях, даже когда моя веб-страница, наконец, загружается и сообщает ios is: undefined, вышеупомянутый метод одновременно записывает Can see JS from obj c в консоль Xcode. Это, по-видимому, указывает на то, что JSContext по-прежнему действителен и что по какой-то причине он просто больше не виден из JS.


Извиняясь за очень длинный вопрос, есть так мало документации по этому поводу, что я понял, что больше информации я мог бы предоставить, тем лучше.

4b9b3361

Ответ 1

Загрузка страницы может привести к тому, что WebView (и UIWebView, который обернет WebView), получит новый JSContext.

Если бы это был MacOS, о котором мы говорили, то, как показано в разделе WebView в предложении WWDC 2013 года "Интеграция JavaScript в собственные приложения" в сети Apple developer (https://developer.apple.com/videos/wwdc/2013/?id=615), вам нужно будет реализовать делегат для загрузки фрейма и инициализировать переменные JSContext в вашей реализации селектора для webView: didCreateJavaScriptContext: forFrame:

В случае IOS вам нужно сделать это в webViewDidFinishLoad:

-(void)webViewDidFinishLoad:(UIWebView *)view{
    self.js = [view valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // Undocumented access to UIWebView JSContext
    self.js[@"ios"] = self;
}

Предыдущий JSContext по-прежнему доступен для Objective-C, поскольку вы сохранили ссылку на него.

Ответ 2

проверьте этот UIWebView JSContext

Ключевым моментом является регистрация объекта javascript после изменения JSContext. Я использую наблюдателя runloop, чтобы проверить, завершена ли какая-либо операция в сети, как только она закончится, я получу измененный JSContext и зарегистрирую любой объект, который я хочу ему.

Я не пытался, если эта работа для iframe, если вам нужно зарегистрировать некоторые объекты в iframe, попробуйте это

NSArray *frames = [_web valueForKeyPath:@"documentView.webView.mainFrame.childFrames"];

[frames enumerateObjectsUsingBlock:^(id frame, NSUInteger idx, BOOL *stop) {
    JSContext *context = [frame valueForKeyPath:@"javaScriptContext"];
    context[@"Window"][@"prototype"][@"alert"] = ^(NSString *message) {
        NSLog(@"%@", message);
    };
}];