Git Product home page Git Product logo

uiwebview-ts_javascriptcontext's Introduction

I've worked on more “hybrid" iOS applications than I care to admit. One of the major pain points of these apps is always communication across the web/native boundary - between JavaScript running in a UIWebView to ObjectiveC in the app shell.

We all know that the only official way to call into a UIWebView from ObjectiveC is via stringByEvaluatingJavaScriptFromString. And the typical way to call out from JavaScript is some manner of setting window.location to trigger a shouldStartLoadWithRequest: callback on the UIWebView delegate. Another oft-considered technique is to implement a custom NSURLProtocol and intercept requests made via XMLHttpRequest.

Apple gives us a public JavaScriptCore framework (part of WebKit) in iOS7, and JavaScriptCore provides simple mechanisms to proxy objects and methods between ObjectiveC and the JavaScript “context”. Unfortunately, while it is common knowledge that UIWebView is built upon WebKit and in turn is using JavaScriptCore, Apple didn’t expose a mechanism for us to access this infrastructure.

It is possible to manhandle UIWebView and retrieve its internal JSContext object by using a KVO keypath to access undocumented properties deep within UIWebView. impathic describes this technique on his blog. A major drawback of this approach, of course, is that it relies on the internal structure of the UIWebView.

I present an alternative approach to retrieving a UIWebView’s JSContext. Of course mine is also non-Apple-sanctioned, and could break also. I probably won’t try shipping this in an application to the App Store. But it is less likely to break, and I think too that it does not have any specific dependency on the internals of UIWebView other than UIWebView itself using WebKit and JavaScriptCore. (There is one small caveat to this, explained later.)

The basic mechanism in play is this: WebKit communicates “frame loading events” to its clients (such as UIWebView) using “WebFrameLoadDelegate” callbacks performed in similar fashion to how UIWebView communicates page loading events via its own UIWebViewDelegate. One of the WebFrameLoadDelegate methods is webView:didCreateJavaScriptContext:forFrame: Like all good event sources, the WebKit code checks to see if the delegate implements the callback method, and if so makes the call. Here’s what it looks like in the WebKit source (WebFrameLoaderClient.mm):

if (implementations->didCreateJavaScriptContextForFrameFunc) {
    CallFrameLoadDelegate(implementations->didCreateJavaScriptContextForFrameFunc, webView, @selector(webView:didCreateJavaScriptContext:forFrame:),
        script.javaScriptContext(), m_webFrame.get());
}

It turns out that in iOS, inside UIWebView, whatever object is implementing WebKit WebFrameLoadDelegate methods doesn’t actually implement webView:didCreateJavaScriptContext:forFrame:, and hence WebKit never performs this call. If the method existed on the delegate object then it would be called automatically.

Well, there are a handful of ways in ObjectiveC to dynamically add a method to an existing class or object. The easiest way is via a category. My approach hinges on extending NSObject via a category, to implement webView:didCreateJavaScriptContext:forFrame:.

Indeed, adding this method prompts WebKit to start calling it, since any object (including some sink object inside UIWebView) that inherits from NSObject now has an implementation of webView:didCreateJavaScriptContext:forFrame:. If the sink inside UIWebView were to implement this method in the future then this approach would likely silently fail since my implementation on NSObject would never be called.

When our method is called by WebKit it passes us a WebKit WebView (not a UIWebView!), a JavaScriptCore JSContext object, and WebKit WebFrame. Since we don’t have a public WebKit framework to provide headers for us, the WebView and WebFrame are effectively opaque to us. But the JSContext is what we’re after, and it’s fully available to us via the JavaScriptCore framework. (In practice, I do end up calling one method on the WebFrame, as an optimization.)

The question becomes how to equate a given JSContext back to a particular UIWebView. The first thing I tried was to use the WebView object we’re handed and walk up the view hierarchy to find the owning UIWebView. But it turns out this object is some kind of proxy for a UIView and is not actually a UIView. And because it is opaque to us I really didn’t want to use it.

My solution is to iterate all of the UIWebViews created in the app (see the code to see how I do this) and use stringByEvaluatingJavaScriptFromString: to place a token “cookie” inside its JavaScriptContext. Then, I check for the existence of this token in the JSContext I’ve been handed - if it exists then this is the UIWebView I’ve Been Looking For!

Once we have the JSContext we can do all sorts of nifty things. My test app shows how we can map ObjectiveC blocks and objects directly into the global namespace and access/invoke them from JavaScript.

One Last Thing

(Sorry, couldn’t resist.) With this bridge in place, it’s now possible to invoke ObjectiveC code in the app via the desktop Safari WebInspector console! Try it out with the test app by attaching the WebInspector and typing sayHello(); into the console input field. Now that’s cool!

Nick Hodapp (a.k.a TomSwift), November 2013

uiwebview-ts_javascriptcontext's People

Contributors

tomswift avatar

Watchers

Fred Chen avatar James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.