25

I'm experimenting with WkWebKit talking back and forth between app and page. I can get javaScript to execute fine using WkWebView evaluateJavascript method, but when I try to execute window.webkit.messageHandlers.myHandler.postMessage('hello world!') on the JavaScript page, I find that window.webkit is not defined.

Odd... I'm running in a simulator of iPad with ios 8.4. I thought this was available in original version 8, no?

I can't find anyone else posting about this, so perhaps I've done something wrong?

I've even attached my Safari Developer to the simulator's browser, and in the console I try to see what window.webkit is, and sure enough, it does not exist.

Note that I add an initial script to run when the page loads (I see this in the javascript editor - the message is logged). And I add a script message handler as well...

[EDIT: Adding more code details here] Here is my code:

- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view, typically from a nib.
  NSLog(@"main view Controller viewDidLoad called...");

// if we are running on an OLD ios (pre v8) WKWebView will not exist.  So don't create it if it doesn't exist...
if (NSClassFromString(@"WKWebView")) {
    // WKWebView cannot be dragged onto storyboard, so have to create it manually here.
    // We have a "ContainerView" called _webContainer that this browser view will live inside
    // First we create a web view configuration object...
    WKWebViewConfiguration *wbConfig = [WKWebViewConfiguration alloc];
    wbConfig.mediaPlaybackAllowsAirPlay = true;
    wbConfig.mediaPlaybackRequiresUserAction = false; 
    // inject some Javascript into our page before it even loads...
    NSString *scriptSource = @"console.log('Hi from the iOS app hosting this page...'); window.hostedByWkWebView=true;";
    WKUserScript *userScript = [[WKUserScript alloc]
                                initWithSource:scriptSource
                                injectionTime:WKUserScriptInjectionTimeAtDocumentStart
                                forMainFrameOnly:YES];

    [wbConfig.userContentController addUserScript:userScript];
    [wbConfig.userContentController addScriptMessageHandler:self name:@"myHandler"]; // javascript to use this would be: window.webkit.messageHandlers.myHandler.postMessage


    // Ok, the config is created, now create the WkWebView instance, passing in this config...
    _webView = [[WKWebView alloc] initWithFrame: [_webContainer bounds] configuration:wbConfig];
    _webView.autoresizesSubviews = true;
    _webView.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
    _webView.navigationDelegate = self;

    // Add the web view to the container view, and tell the container to automatically resize its subviews, height and width.
    [_webContainer addSubview:_webView];
    _webContainer.autoresizesSubviews = true;
    _webContainer.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;

    // Set the URL for the webview and navigate there...
    NSString *fullURL = @"https://myurlgoeshere.com";

    NSURL *url = [NSURL URLWithString:fullURL];
    NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];

    [_webView loadRequest:requestObj];
}
else {
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Cannot create Web View" message:@"The web view used requires iOS 8 or higher.  Sorry." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:@"", nil];
    [alert show];
}

//...
Brian B
  • 1,509
  • 3
  • 20
  • 29

6 Answers6

51

I solved it and problem was that if you set userContentController to a new object, that userContentController's WKScriptMessageHandlers will not be registered correctly in Apple's internal code:

WKUserContentController *userContentController = [WKUserContentController new];
userContentController.addScriptMessageHandler(self, name: "jockey")
userContentController.addScriptMessageHandler(self, name: "observe")
webView.configuration.userContentController = userContentController

fixed by using the already instantiated userContentController by Apple:

webView.configuration.userContentController.addScriptMessageHandler(self, name: "jockey")
webView.configuration.userContentController.addScriptMessageHandler(self, name: "observe")
keaukraine
  • 5,315
  • 29
  • 54
Pascal Kaufmann
  • 526
  • 1
  • 5
  • 3
  • 2
    you've saved my afternoon.. wrestling w/ that one a bit. thank you! ;) – Sean Jan 12 '17 at 18:50
  • 3
    This is the right answer and solved my issue. Any idea why we have to use the existing one and not create a new one? When we create a new one and set it, e.g. self.wkWebView.configuration.userContentController = userContentController; - does the new one get ignored? The property is not read only. – Rob B Feb 01 '17 at 03:12
  • 1
    That's the one! – mkeremkeskin Jul 27 '17 at 13:55
  • 4
    Wow I found this answer after an hour of wrestling with the code. WHY though?? If there's a setter available why shouldn't we be able to use a custom one? It's a bit of a bad API design on WKWebView. – Eloy_007 Aug 16 '18 at 23:09
  • thank you -- we were totally stuck until we found this post. – Nik Bhatt Oct 03 '20 at 20:50
  • The doc states that webView.configuration is a copy of the configuration. Setting something on the copy is not used. But obviously, the userContentController is still a reference to the original one which can be changed. – theguy Oct 18 '22 at 08:20
18

The window.webkit namespace only appears in webview with script message handlers. Make sure that you have called addScriptMessageHandler method of WKUserContentController.

soflare
  • 751
  • 5
  • 16
  • Interesting, well, I've added more coding details above and you will find that I do indeed add a ScriptMessageHandler to the WKUserContentController... I also add an initial script to the content controller as well, and I see that does run. – Brian B Sep 25 '15 at 15:57
  • Hmmm. While my main ViewController (whose code is above) does handle the userContentController:didReceiveScriptMessage message, it is not actually a WKScriptMessageHandler object. So I do get a compiler warning but I was under the impression it would work still anyway... – Brian B Sep 25 '15 at 16:02
  • Ok, I'm not sure why it is working now, I didn't change much! Maybe I was up too late working on it. Anyway, I changed the subclass of the ViewController to a WKScriptMessageHandler and it worked fine. Well, I also tried with iPhone 5 at iOS 8.4. But then I put the VewController back to the way it was, tried on iPad iOS 8.4 simulator, and, well, it worked fine again. I did notice that in Safari debugging the javascript in the console, I could make the call to webkit.messageHandlers... but it didn't show webkit is defined. Odd, but it works. – Brian B Sep 25 '15 at 16:18
  • You forget call init method of WKWebViewConfiguration. – soflare Sep 25 '15 at 17:14
  • So I did... I'm new to Objective-C, so I didn't notice that myself. – Brian B Sep 28 '15 at 16:14
9

I ran into this SO because I was experiencing the same issue and this is how I solved it using a custom CustomContentController (subclass of WKUserContentController) instance.

let userContentController = CustomContentController()
let webViewConfiguration = WKWebViewConfiguration()
webViewConfiguration.userContentController = userContentController
webView = WKWebView(frame: .zero, configuration: webViewConfiguration)

In my case the CustomContentController is a subclass of WKUserContentController in which the add(_ scriptMessageHandler: WKScriptMessageHandler, name: String) method is called, but I don't believe that is significant.

I believe that the WKUserContentController must be instantiated and applied to a WKWebViewConfiguration before the WKWebView is initialized via WKWebView(frame: .zero, configuration: webViewConfiguration)

If the WKWebView has been created and then you try change the WKWebViewConfiguration you will encounter window.webkit not being available in the JSContext.

David Anderson
  • 221
  • 3
  • 6
1

I met the same problem. And after wasting 2 hours, I found this below to work fine. But I don't know why.

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    WKUserContentController *userCctrl = [[WKUserContentController alloc] init];
    [userCctrl addScriptMessageHandler:self name:@"jscall"];
    WKWebViewConfiguration *wbConfiger = [[WKWebViewConfiguration alloc] init];
    wbConfiger.userContentController = userCctrl;
    CGSize size = [UIScreen mainScreen].bounds.size;
    WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectMake(0, 20, size.width, size.height - 20)  configuration:wbConfiger];
    [self.view addSubview:webView];
    webView.UIDelegate = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [webView evaluateJavaScript:@"if (window.webkit === undefined) { alert('未注册的 window.webkit'); } else { window.webkit.messageHandlers.jscall.postMessage({title:'标题'}); }" completionHandler:^(id obj, NSError *error) {
            NSLog(@"run js completion with error = %@", error);
    }];
    });
}
#pragma mark -
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler {
    NSLog(@"run js alert panel message = %@", message);
    completionHandler();
}
#pragma mark -
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message {
    NSLog(@"wkwebview run javascript name = %@, body = %@", message.name, message.body);
}
shaojunx
  • 11
  • 2
1

The solution for me at the end worked enabling the javascript on the webview with

webview?.configuration.preferences.javaScriptEnabled = true
0

According apple's documentation: WKUserContentController documentation

https://developer.apple.com/documentation/webkit/wkusercontentcontroller

You need to create your userContentController, and add script message handlers

userContentController = WKUserContentController()
userContentController.addScriptMessageHandler(foo, name: "foo")

BEFORE creating the webview

let configuration = WKWebViewConfiguration()
configuration.userContentController = userContentController
let webview = WKWebView(frame: .zero, configuration: configuration)

doing it in the AFTER would result in error like:

undefined is not an object (evaluating 'window.webkit.messageHandlers.foo.postMessage')
Dylan Liu
  • 123
  • 1
  • 7
  • Would you mind [edit]ing your answer to include the bit from Apple as text? You can format it as a block quote so it's clear that it's an excerpt from them. That will make it easier for people to read if they rely on e.g. screen readers and/or translators. It might also help with indexing of the page for people with similar questions. – Jeremy Caney Apr 20 '22 at 03:49