Embedded Webkit - script callbacks how?
Asked Answered
T

2

13

On windows, when the "Shell.Explorer" ActiveX control is embedded in an application it is possible to register an "external" handler - on object that implements IDispatch, such that scripts on the web page can call out to the hosting application.

<button onclick="window.external.Test('called from script code')">test</button>

Now, ive moved to Mac development and thought I could get something similar working from WebKit embedded in my Cocoa application. But, there really doesn't seem to be any facility to allow scripts to call back out to the hosting application.

One piece of advice was to hook window.alert and get scripts to pass a formatted message string as the alert string. Im also wondering if WebKit can perhaps be directed to an application hosted NPAPI plugin using NPPVpluginScriptableNPObject.

Am I missing something? Is it really this hard to host a WebView and allow scripts to interact with the host?

Temporize answered 18/2, 2010 at 12:34 Comment(0)
C
31

You need to implement the various WebScripting protocol methods. Here is a basic example:

@interface WebController : NSObject
{
    IBOutlet WebView* webView;
}

@end

@implementation WebController

//this returns a nice name for the method in the JavaScript environment
+(NSString*)webScriptNameForSelector:(SEL)sel
{
    if(sel == @selector(logJavaScriptString:))
        return @"log";
    return nil;
}

//this allows JavaScript to call the -logJavaScriptString: method
+ (BOOL)isSelectorExcludedFromWebScript:(SEL)sel
{
    if(sel == @selector(logJavaScriptString:))
        return NO;
    return YES;
}

//called when the nib objects are available, so do initial setup
- (void)awakeFromNib
{
    //set this class as the web view's frame load delegate 
    //we will then be notified when the scripting environment 
    //becomes available in the page
    [webView setFrameLoadDelegate:self];

    //load a file called 'page.html' from the app bundle into the WebView
    NSString* pagePath = [[NSBundle mainBundle] pathForResource:@"page" ofType:@"html"];
    NSURL* pageURL = [NSURL fileURLWithPath:pagePath];
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:pageURL]];
}


//this is a simple log command
- (void)logJavaScriptString:(NSString*) logText
{
    NSLog(@"JavaScript: %@",logText);
}

//this is called as soon as the script environment is ready in the webview
- (void)webView:(WebView *)sender didClearWindowObject:(WebScriptObject *)windowScriptObject forFrame:(WebFrame *)frame
{
    //add the controller to the script environment
    //the "Cocoa" object will now be available to JavaScript
    [windowScriptObject setValue:self forKey:@"Cocoa"];
}

@end

After implementing this code in your controller, you can now call Cocoa.log('foo'); from the JavaScript environment and the logJavaScriptString: method will be called.

Communion answered 19/2, 2010 at 0:30 Comment(6)
Does [webView mainFrame] loadData: fire the didClearWindowObject: ? I have the [webView setFrameLoadDelegate:self]; setup but this is not setting up the windowScriptObject when I try a breakpoint.Callida
I got it running this way around, but how do you call a handler (JS function) from Cocoa, as a callback that something happened in Cocoa. Of course you can get the windowScriptObject from the WebView but is there a way for Cocoa to know to which WebScriptObject instance it belongs?Apograph
BE AWARE!!!! [windowScriptObject setValue:self forKey:@"Cocoa"]; keeps a strong reference and the given code (passing self) will create a retain cycle (was just solving this issue)Nous
Doc: developer.apple.com/library/mac/documentation/AppleApplications/…Resource
BTW, when you create a default Cocoa app in XCode 7, there are two problems one has to overcome first to use Webkit. You have to click Project Navigator > General > Linked Frameworks and Libraries, and add WebKit.Framework. Then, you have to take your classes and add #import <WebKit/WebKit.h> in order to not get an error when you make an outlet for your webview component in that given class.Aureolin
Also, when you create a default Cocoa app in XCode 7, it creates an AppDelegate.m. I was able to cut/paste your code on this answer from webScriptNameForSelector to the ending didClearWindowObject curly brace, and not have to use the rest. Also, since I was setting the index.html from my applicationDidFinishLaunching, I found that I could comment out the URL loader that you had in awakeFromNib. However, in awakeFromNib, I still had to call [self.wv setFrameLoadDelegate:self];.Aureolin
S
1

This is very easy to do with the WebScriptObject API in combination with the JavaScriptCore framework.

Sarpedon answered 18/2, 2010 at 16:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.