Calling [JSValue callWithArguments:] locks UI when alert() is called
Asked Answered
N

1

7

Using the JSContext from a UIWebView I have created a javascript function that is implemented as an Objective C block:

JSContext *js = ... //get contect from web view
js[@"aFunc"] = ^(JSValue *aString, JSValue *callback) {
    NSString *realString = [aString toString];
    MyOperation *op = [[MyOperation alloc] initWithString:realString andCallback:callback];

    //Do some heavy lifting in background
    [self.myQueue addOperation:op];
}

This function takes a callback as an argument and performs some work in an NSOperationQueue before calling the callback like:

- (void)main {
    JSValue *arg = [self theHeavyWork];
    //Now we have finished the heavy work, switch back to main thread to run callback (if any).
    if ([self.callback isObject] != NO) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.callback callWithArguments:@[arg]];
        });
    }
}

This works fine, unless the callback contains a call to alert():

//This javascript is part of the page in the UIWebView
window.aFunc("important information", function(arg) { alert("Got " + arg); });

In this case the alert shows and the UI becomes completely unresponsive. I am assuming that the event the touch event to close the alert is being blocked by the alert being there.

If I call the callback without the dispatch (in other words on which ever thread the MyOperation is running on) it works just fine, but I was under the impression that any code that could have UI implications (in other words any JS callbacks) should always be run on the main thread. Am I missing something, or do is it really impossible to safely use alert() when using the JavaScriptCore framework?

Nibbs answered 5/4, 2014 at 4:21 Comment(4)
I'm not sure I understand your question. Can you please post the full code you're suing, and clearly describe what happens vs what you expected to happen?Culex
@AbhiBeckert Edited as requested to make how I am handling threads a little clearerNibbs
Interestingly, enqueuing the callback using [NSObject performSelectorOnMainThread:withObject:waitUntilDone] does not seem to cause the deadlock that dispatch_async does.Nibbs
I'd say that is your answer then, you should post it and accept your own answer :). performSelectorOnMainThread is something like 10,000 times slower than dispatch_async. It does a whole bunch of additional stuff that is skipped by dispatch_async to improve performance. I don't know why JavaScriptCore requires it, but apparently it does. You could file a bug in Radar if you want.Culex
N
9

After a couple days looking at stack traces of threads waiting for each other, the solution was so simple I'm not surprised I overlooked it in favor of trying more complicated stuff.

If you want to call back into a UIWebView's javascript asynchronously, use window.setTimeout and let the JSVirtualMachine take care of queuing the callback.

Just replace

dispatch_async(dispatch_get_main_queue(), ^{
    [self.callback callWithArguments:@[arg]];
});

with

dispatch_async(dispatch_get_main_queue(), ^{
    [self.callback.context[@"setTimeout"] callWithArguments:@[self.callback, @0, arg]];
});
Nibbs answered 11/4, 2014 at 15:2 Comment(1)
I find it hard to believe but it does work. WebKit is causing so much grieve.Unchain

© 2022 - 2024 — McMap. All rights reserved.