Access javascripts functions from source WKWebView (window.opener)

Viewed 1023

In my iOS app, I have 2 WKWebView controls to simulate parent/child relationship of 2 tab/window in our web app so that we can reuse all those javascripts. Problem is that there is no generic communication channel between these 2 WKWebViews.

Web Architecture:
- The first/master window loads our internal javascript functions with links opening in second/child window.
- The second/child window loads third party web content.
- The third party content communicates with our system using javascript form source window (using window.opener / window.parent API bridge).

Now, in the native app, this is what I'm doing to reuse existing javascripts and simulate web archtecture -

a. Below js call from the webpage in 1st WKWebView -

  window.open("http://localhost/child.html", "myWindow", "width=200,height=100");  

b. The WKUIDelegate intercepts this call (a) and opens 2nd WKWebView -

- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    // Here, window.open js calls are handled by loading the request in another/new web view
    UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    WebViewController *webViewController = [mainStoryboard instantiateViewControllerWithIdentifier:@"WebViewController"];
    webViewController.loadURLString = navigationAction.request.URL.absoluteString;

    // Show controller without interfering the current flow of API call, thus dispatch after a fraction of second
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.navigationController pushViewController:webViewController animated:YES];
    });
    return nil;
}

c. In web/computer browser, the child window of web app is able to access source js functions using window.opener and window.parent

How to achieve the same functionality in WKWebViews? In other words, js functions in 2nd WKWebView could call js functions from parent/source WKWebView?

2 Answers

You definitely should not return nil from webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:, but an instance of your WKWebView.

Unfortunately, I have no way to verify this right now. But as far as I remember, the trick is also to not ignore WKWebViewConfiguration argument from webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures: delegate method, but create a second WKWebView instance, passing this argument as one of the initWithFrame:configuration: parameters.

Also, according Apple documentation, looks like you don't need to load the request yourself if you're returning correct web view instance. WebKit should handle this for you.

Overall your code should look similar to:

- (nullable WKWebView *)webView:(WKWebView *)webView createWebViewWithConfiguration:(WKWebViewConfiguration *)configuration forNavigationAction:(WKNavigationAction *)navigationAction windowFeatures:(WKWindowFeatures *)windowFeatures {
    WKWebView *childWebView = [[WKWebView alloc] initWithFrame:CGFrameZero configuration:configuration];
    WebViewController *webViewController = [[WebViewController alloc] initWithWebView: childWebView];

    // looks like this is no longer required, but I can't check it now :(
    // [childWebView loadRequest:navigationAction.request];

    // Show controller without interfering the current flow of API call, thus dispatch after a fraction of second
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [self.navigationController pushViewController:webViewController animated:YES];
    });
    return childWebView;
}

A simpler way to accomplish your task will be to have one WKWebView, and use HTML to separate header and detail. The simplest solution is to have one HTML document with the header and all of the details, but if you need to add details dynamically that can also be done use the DOM. Reusing Javascript for both the header and detail will be easiest with this approach.

Related