Adding JS to C++ callback functionality to WebBrowserComponent


#1

JUCE uses the native macOS WebView in the WebBrowserComponent so theoretically this should be quite possible. I have added (what I believe to be) the appropriate methods in the JUCEWebClickDetector class which now looks like this:

        DownloadClickDetectorClass()  : ObjCClass<NSObject> ("JUCEWebClickDetector_")
    {
        addIvar<WebBrowserComponent*> ("owner");
        
        addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:),
                   decidePolicyForNavigationAction, "v@:@@@@@");
        addMethod (@selector (webView:decidePolicyForNewWindowAction:request:newFrameName:decisionListener:),
                   decidePolicyForNewWindowAction, "v@:@@@@@");
        addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@");
        addMethod (@selector (webView:didFailLoadWithError:forFrame:),  didFailLoadWithError,  "v@:@@@");
        addMethod (@selector (webView:didFailProvisionalLoadWithError:forFrame:),  didFailLoadWithError,  "v@:@@@");
        addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@");
        addMethod (@selector (webView:runOpenPanelForFileButtonWithResultListener:allowMultipleFiles:), runOpenPanel, "v@:@@", @encode (BOOL));
        
        addMethod (@selector (isSelectorExcludedFromWebScript:), webview_is_selector_excluded_from_web_script, "c@::", @encode (BOOL));
        addMethod (@selector (webScriptNameForSelector:), webview_webscript_name_for_selector, "c@::" );
        addMethod (@selector (webView:didClearWindowObject:forFrame:), webview_did_clear_window_object, "v@:@@@" );
        addMethod (@selector (invoke:), webview_external_invoke, "v@:@" );
        
        registerClass();
    }
    
    static void setOwner (id self, WebBrowserComponent* owner)   { object_setInstanceVariable (self, "owner", owner); }
    static WebBrowserComponent* getOwner (id self)               { return getIvar<WebBrowserComponent*> (self, "owner"); }
    
    static BOOL webview_is_selector_excluded_from_web_script(id self, SEL cmd,
                                                             SEL selector) {
        return selector != @selector(invoke:);
    }
    
    static NSString *webview_webscript_name_for_selector(id self, SEL cmd,
                                                         SEL selector) {
        return selector == @selector(invoke:) ? @"invoke" : nil;
    }
    
    static void webview_did_clear_window_object(id self, SEL cmd, id webview,
                                                id script, id frame) {
        [script setValue:self forKey:@"external"];
    }
    
    static void webview_external_invoke(id self, SEL cmd, id arg) {
        std::cout << "JS Click" << std::endl;
    }
   ...

When I run the app with some breakpoints it will break on the webview_did_clear_window_object function but nothing happens when I click the HTML button on my page. From what I can tell, no other functions are being called (not breaking at breakpoints).

This is my first time modding any of the juce modules directly so it is entirely possible that I am missing something simple here but I just can’t seem to figure out what the issue is here.

For reference, here is the HTML file I am loading:

<!DOCTYPE html>
<html>
<body>

<button type="button" onclick="myFunction()">Click Me!</button>

<script>
function myFunction() {
    window.external.invoke();
}
</script>

</body>
</html>

#2

The function signatures that you have provided for the methods in addMethod() look incorrect. For example webScriptNameForSelector should be @@:: as it returns an NSString*.


#3

Unfortunately this does not seem to be the issue as the method signatures are the same ones I used in a non-juce C++ app while testing. Also, if that were the problem, a segmentation fault would pop up at some point.

I have a few theories right now:

  1. isSelectorExcludedFromWebScript, webScriptNameForSelector, and invoke need to be added to the NSWindow delegate

  2. There is a threading issue of some sort somewhere (yes, very descriptive).

  3. All the added methods need to be re-added on page load. Can’t imagine why but I would try anything at this point.

  4. For some reason, the JS isn’t being executed


#4

If any one else is trying to do this, the reason what I have is not working is because these methods need to be in the main thread/dispatch queue.