Calling a C Function From JS

Let's say that we have a page with the following HTML/JS on it:

<html>
  <head>
  </head>
  <body>
    <button onclick="OnButtonClick();">Click Me</button>
    <div id="result"></div>
  </body>
</html>

Our goal is to call a C function bound to OnButtonClick() when a user clicks the button and display a message in <div id="result"></div>.

Let's dive in.

About JSObjectMakeFunctionWithCallback()

JavaScriptCore provides a method to bind static C callbacks to JavaScript via JSObjectMakeFunctionWithCallback().

You can use this method to create JS Function Objects that will call a C function when invoked.

Function Signature

We can only bind static C functions with a certain function signature (typedef'd as JSObjectCallAsFunctionCallback).

📘

See the API declaration for JSObjectCallAsFunctionCallback here for a deeper description of each of the callback parameters.

JSValueRef MyCallback(JSContextRef ctx, JSObjectRef function,
  JSObjectRef thisObject, size_t argumentCount, 
  const JSValueRef arguments[], JSValueRef* exception) {
  
  // Handle JavaScript arguments in our C callback here...
  
  // Optionally return a value back to JavaScript
  
  return JSValueMakeNull(ctx);
}

Using JSObjectMakeFunctionWithCallback()

We will create a function object named OnButtonClick using our C callback and bind it to the global object to expose it to the page.

📘

See the API declaration for JSObjectMakeFunctionWithCallback() here.

// This callback will be bound to 'OnButtonClick()' on the page.
JSValueRef OnButtonClick(JSContextRef ctx, JSObjectRef function,
  JSObjectRef thisObject, size_t argumentCount, 
  const JSValueRef arguments[], JSValueRef* exception) {

  const char* str = 
    "document.getElementById('result').innerText = 'Ultralight rocks!'";
  
  // Create our string of JavaScript
  JSStringRef script = JSStringCreateWithUTF8CString(str);

  // Execute it with JSEvaluateScript, ignoring other parameters for now
  JSEvaluateScript(ctx, script, 0, 0, 0, 0);

  // Release our string (we only Release what we Create)
  JSStringRelease(script);

  return JSValueMakeNull(ctx);
}

// Use LoadListener::OnDOMReady to wait for the DOM to load.
void MyApp::OnDOMReady(View* caller,
                       uint64_t frame_id,
                       bool is_main_frame,
                       const String& url) {
  
  // Acquire the JS execution context for the current page.
  auto scoped_context = caller->LockJSContext();
  
  // Typecast to the underlying JSContextRef.
  JSContextRef ctx = (*scoped_context);
  
  // Create a JavaScript String containing the name of our callback.
  JSStringRef name = JSStringCreateWithUTF8CString("OnButtonClick");

  // Create a garbage-collected JavaScript function that is bound to our
  // native C callback 'OnButtonClick()'.
  JSObjectRef func = JSObjectMakeFunctionWithCallback(ctx, name, 
                                                      OnButtonClick);
  
  // Get the global JavaScript object (aka 'window')
  JSObjectRef globalObj = JSContextGetGlobalObject(ctx);

  // Store our function in the page's global JavaScript object so that it
  // accessible from the page as 'OnButtonClick()'.
  JSObjectSetProperty(ctx, globalObj, name, func, 0, 0);

  // Release the JavaScript String we created earlier.
  JSStringRelease(name);
}