-
Notifications
You must be signed in to change notification settings - Fork 614
Writing V8 Extensions
All of the JavaScript functions in the brackets.fs
and brackets.app
objects are backed by native code in the application shell. This document describes how it all works.
You should read the [Architecture Overview] (https://github.com/adobe/brackets-shell/wiki/Architectural-Overview) document before reading this document.
The native code is hooked up to JavaScript through a V8 Extension. General information on V8 Extensions (along with other ways to integrate with JavaScript in CEF) can be found on the CEF wiki: http://code.google.com/p/chromiumembedded/wiki/JavaScriptIntegration
There are quite a few parts to writing a V8 Extension. Complicating things even more is the fact that CEF3 runs in a multi-process environment. JavaScript execution happens in the render process, but all other execution, including UI, happens in the browser process.
The JavaScript side of the extension is all done in the appshell/appshell_extensions.js
file. This file defines and exports the brackets.fs
and brackets.app
objects, and also defines the native bindings.
Native bindings are functions marked with the native
keyword. For example:
native function ShowOpenDialog();
These functions do not have a function body, and you do not declare any parameters. The function body is implemented in native code (see the next step), and parameters can be passed to the function.
ShowOpenDialog(callback, allowMultipleSelection, chooseDirectory, ...);
All async functions must pass a callback function as the first parameter. This callback function will be called once the operation is complete.
The binding from JavaScript to native code is done in the appshell/client_app.cpp
file. The first step is defining the V8 Extension itself, which is done in the ClientApp::OnWebKitInitialized()
function. The call to CefRegisterExtension()
binds all native function calls to the AppShellExtensionHandler
class.
Whenever a native function is called, AppShellExtensionHandler::Execute()
is invoked. This code runs in the render process, so only the most trivial extension code should be executed here. For Brackets, only getElapsedMilliseconds()
is handled here. All others calls are passed to the browser process via a CefProcessMessage
.
Before sending a message to the browser process, the callback function is saved off so it can be called later. A unique id is used for every message sent.
At this point the render process returns to its normally scheduled processing. The extension continues to execute asynchronously on the browser process.
This is where the bulk of the work occurs. Messages sent from the render process are received in the ProcessMessageDelegate
class in the appshell/appshell_extensions.cpp
file. The OnProcessMessageReceived
method checks the arguments, calls platform-specific functions (if needed), and sends a message back to the render process. The argument checking and platform-specific code is different for each message received.
Platform-specific functions are declared in appshell/appshell_extensions_platform.h
and defined in appshell/appshell_extensions_mac.mm
(for mac) and appshell/appshell_extensions_win.cpp
(for windows).
Once the message has been processed in the browser process, the callback function needs to be invoked. However, this function must be invoked from the render process, so another CefProcessMessage
is created.
The invokeCallback message is created for you in appshell_extensions.cpp
. All your code needs to do is add any additional arguments to the responseArgs
list. The error argument is added for you, and the message is sent.
Back in the OnProcessMessageRecieved
method in appshell/client_app.cpp
, the invokeCallback request is handled. This will call the JavaScript callback function, passing the parameters specified earlier.
The good news is most extensions only require changes to appshell/appshell_extensions.js
and appshell/appshell_extensions.cpp
(and platform variants). Execution of your extension will pass through the other steps, but no changes should be required.