title: JavaScript synchronous native communication to WKWebView
body: <p>Is synchronous communication between JavaScript and Swift/Obj-C native code possible using the WKWebView?</p>
  
  <p>These are the approaches I have tried and have failed.</p>
  
  <p><strong><em>Approach 1: Using script handlers</em></strong></p>
  
  <p><code>WKWebView</code>'s new way of receiving JS messages is by using the delegate method <code>userContentController:didReceiveScriptMessage:</code> which is invoked from JS by <code>window.webkit.messageHandlers.myMsgHandler.postMessage('What's the meaning of life, native code?')</code>
  The problem with this approach is that during execution of the native delegate method, JS execution is not blocked, so we can't return a value by immediately invoking <code>webView.evaluateJavaScript("something = 42", completionHandler: nil)</code>.</p>
  
  <p><em>Example (JavaScript)</em></p>
  
  <pre><code>var something;
  function getSomething() {
      window.webkit.messageHandlers.myMsgHandler.postMessage("What's the meaning of life, native code?"); // Execution NOT blocking here :(
      return something;
  }
  getSomething();    // Returns undefined
  </code></pre>
  
  <p><em>Example (Swift)</em></p>
  
  <pre><code>func userContentController(userContentController: WKUserContentController, didReceiveScriptMessage message: WKScriptMessage) {
      webView.evaluateJavaScript("something = 42", completionHandler: nil)
  }
  </code></pre>
  
  <p><strong><em>Approach 2: Using a custom URL scheme</em></strong></p>
  
  <p>In JS, redirecting using <code>window.location = "js://webView?hello=world"</code> invokes the native  <code>WKNavigationDelegate</code> methods, where the URL query parameters can be extracted. However, <em>unlike the UIWebView</em>, the delegate method is not blocking the JS execution, so immediately invoking <code>evaluateJavaScript</code> to pass a value back to the JS doesn't work here either.</p>
  
  <p><em>Example (JavaScript)</em></p>
  
  <pre><code>var something;
  function getSomething() {
      window.location = "js://webView?question=meaning" // Execution NOT blocking here either :(
      return something;
  }
  getSomething(); // Returns undefined
  </code></pre>
  
  <p><em>Example (Swift)</em></p>
  
  <pre><code>func webView(webView: WKWebView, decidePolicyForNavigationAction navigationAction: WKNavigationAction, decisionHandler decisionHandler: (WKNavigationActionPolicy) -&gt; Void) {
      webView.evaluateJavaScript("something = 42", completionHandler: nil)
      decisionHandler(WKNavigationActionPolicy.Allow)
  }
  </code></pre>
  
  <p><strong><em>Approach 3: Using a custom URL scheme and an IFRAME</em></strong></p>
  
  <p>This approach only differs in the way that <code>window.location</code> is assigned. Instead of assigning it directly, the <code>src</code> attribute of an empty <code>iframe</code> is used.</p>
  
  <p><em>Example (JavaScript)</em></p>
  
  <pre><code>var something;
  function getSomething() {
      var iframe = document.createElement("IFRAME");
      iframe.setAttribute("src", "js://webView?hello=world");
      document.documentElement.appendChild(iframe);  // Execution NOT blocking here either :(
      iframe.parentNode.removeChild(iframe);
      iframe = null;
      return something;
  }
  getSomething();
  </code></pre>
  
  <p>This nonetheless, is not a solution either, it invokes the same native method as Approach 2, which is not synchronous.</p>
  
  <p><strong><em>Appendix: How to achieve this with the old UIWebView</em></strong></p>
  
  <p><em>Example (JavaScript)</em></p>
  
  <pre><code>var something;
  function getSomething() {
      // window.location = "js://webView?question=meaning" // Execution is NOT blocking if you use this.
  
      // Execution IS BLOCKING if you use this.
      var iframe = document.createElement("IFRAME");
      iframe.setAttribute("src", "js://webView?question=meaning");
      document.documentElement.appendChild(iframe);
      iframe.parentNode.removeChild(iframe);
      iframe = null;
  
      return something;
  }
  getSomething();   // Returns 42
  </code></pre>
  
  <p><em>Example (Swift)</em></p>
  
  <pre><code>func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -&gt; Bool {
      webView.stringByEvaluatingJavaScriptFromString("something = 42")    
  }
  </code></pre>