Son of a Beach Son of a Beach - 10 months ago 55
iOS Question

Unable to run proj4.js in JavaScriptCore JSContext

I've been unable to run proj4.js (projections Javascript, ) in an iOS Objective-C JavaScriptCore JSContext. After attempting to run it in the context, the proj4 functions are not available to the subsequent Javascript code that I attempt to run in the same JSContext.

After enabling Javascript exception handling on the JSContext, I find that I get the following exception when attempting to run proj4.js:

TypeError: undefined is not an object (evaluating 'b.proj4=a()')

I have successfully run the same proj4.js in a WKWebView via some HTML that loads the script. Works fine there. But I now need it to run in a JSContext where I an use it and convert objects from Javascript output to Objective-C objects.

Here's the code that fails:

NSString *proj4Path = [[NSBundle mainBundle] pathForResource:@"proj4" ofType:@"js" inDirectory:@"proj4"];
NSString *proj4JS = [NSString stringWithContentsOfFile:proj4Path usedEncoding:nil error:nil];
JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"JAVASCTIPT EXCEPTION: %@", exception);
[context evaluateScript:proj4JS]; // FAILS HERE

If I replace that last line with a very simple JavaScript it works fine. Eg,

[context evaluateScript:@"1 + 1"];

So it appears that the context itself is functional. Perhaps there is some problem with the environment or config of the JSContext, but I can't think of anything.

EDIT: The line in proj4.js that causes the exception is:

"undefined"!=typeof window?b=window:"undefined"!=typeof global?b=global:"undefined"!=typeof self&&(b=self),b.proj4=a()

So this would indicate that neither 'window' nor 'global' nor 'self' are defined in the JSContext environment (I think?). I'm new to JavaScriptCore (and moderately new to JavaScript), so I'm not sure where to go from here (but I will continue to research).

Answer Source

As per the edit to my question, neither 'window' nor 'global' nor 'self' appear to be defined in a new JSContext. This makes sense for 'window' (not so sure about for 'global' or 'self'!!!).

So there are two possible fixes that I came up with:

  1. This first fix works for proj4.js, but does not work for two subsequent JavaScripts that I needed to run. This fix was to simply add the following line of Objective-C code before attempting to run the proj4.js:

    [context evaluateScript:@"var global = this;"];

then the problem line (the last line in my original code in the original question) runs fine!

  1. This fix is more comprehensive in that it should work for any JavaScript that can run in a normal web browser. However, it is more heavy-handed. For this solution the only change required is to replace the JSContext 'init' line to get the JSContext from a UIWebView:

    JSContext * context = [[[UIWebView alloc] init] valueForKeyPath : @"documentView.webView.mainFrame.javaScriptContext" ];

Although I'd prefer to avoid instantiating a web view, there are just too many missing components to use a raw JSContext for complicated Javascripts. I'd also prefer to use a WKWebView rather than a UIWebView, but at present, the JSContext of a WKWebView is not available.