Introduction
A bunch of HTML + CSS + JavaScript today has established itself as a universal way to build user interfaces. And not only in web applications, but also in desktop and mobile applications. Examples of this are metro-applications in Windows 8, the
PhoneGap framework for creating mobile applications.
Interface implementation using HTML, CSS, and JavaScript primarily means that the interface will be displayed in a browser. If we are considering a desktop or mobile application, then, obviously, the browser must be embedded.
In this article we will look at using
WebKit .NET in a desktop C # application for Windows.
')
Embedded Browser Requirements
Identify the basic requirements for embedded browser.
First of all, from C #, we should be able to set up an arbitrary content for the HTML document — that is, our interface. Next, there naturally arises the need for communication between C # and JavaScript. You must be able to call C # from JavaScript and JavaScript from C #, and in two ways - synchronously and asynchronously. Our desktop application with an interface implemented in an embedded browser is in many ways similar to a traditional web application that has a client-server architecture. Therefore, the presence of asynchronous C # calls from JavaScript is very important. These calls are analogous to AJAX requests in a traditional web application.
Web Inspector and Debugger is the best friend of every web developer. And what about this here? Let's say right away - here it is not so simple. We were unable to find a way to debug a javaScript running in an embedded browser, but we found an opportunity to explore the dom and got a javascript console.
Thus, the basic requirements for WebKit .NET were the presence of the following things:
- ability to set HTML document any content
- JavaScript calls from C # synchronously or asynchronously
- C # calls from javascript synchronously or asynchronously
- web inspector
Next, we consider what of the above was present in WebKit .NET and how the missing features were implemented.
Setting HTML Document Contents
The
WebKitBrowser.DocumentText property allows
you to set arbitrary content for an HTML document. Our interface is completely independent of external resources, we include all JavaScript and CSS directly in HTML. To increase performance, all scripts are included in the minifitsirovanny form.
JavaScript calls from C #
For JavaScript calls from C # in WebKit .NET there is a
Document.InvokeScriptMethod method with the following signature:
public Object InvokeScriptMethod( string Method, params Object[] args )
Unfortunately, this method has a problem with passing parameters to the JavaScript function - it just does not work.
To solve this problem, we had to develop our own JavaScript call protocol from C #. The idea is this:
- To pass parameters, use a div with the specified identifier.
- C # serializes the name and an array of parameters for the JavaScript function into a JSON string and places it in the specified div
- JavaScript extracts JSON, deserializes it and calls the specified function.
The code for invoking a JavaScript function in C # is as follows:
public object CallJS(string functionName, object[] arguments, bool async) { var dict = new Dictionary<string, object>(); dict["arguments"] = arguments; dict["functionName"] = functionName; dict["async"] = async; SetJsBuffer(dict); return webKitBrowser.Document.InvokeScriptMethod("JS_CALL_HANDLER"); } private void SetJsBuffer(object data) { string id = "cs-js-buffer"; Element elem = null; try { elem = webKitBrowser.Document.GetElementById(id); } catch (Exception) { }
And in JavaScript, the call is handled like this:
JS_CALL_HANDLER = function() {
To make the call asynchronous, we simply call the specified function with
a delay of 1ms. Since we do not need asynchronous calls that return a value,
then such a solution fully satisfies us.
C # Calls from JavaScript
There is no regular C # call mechanism from JavaScript in WebKit .NET. After a close look at the documentation, the
WebKitBrowser.DocumentTitleChanged event was found. This is probably the only event that JavaScript can easily generate at any time by setting document.title.
There are two things that make this event attractive:
- document.title can be set large enough, more than 16Kb
- The installation of document.title in JavaScript is completed only after the execution of all event handlers for the DocumentTitleChanged event in C #
This formed the basis of our C # call protocol from JavaScript.
The following listing shows JavaScript code intended for C # calls.
var callMap = {};
As you can see from the listing, each call is provided with a unique identifier.
It is then used as the identifier of the div element in which C # places the result.
If the call is synchronous, just before its completion, the C # handler can
put your result in the body of the HTML document. In this case, the callCSharp method can
extract the result from the dom immediately after setting document.title.
For asynchronous calls, C # should initiate the start of callback functions upon completion.
handlers. In this case, from C # we call the special JavaScript method shown in the following listing:
COMMON_ASYNC_HANDLER = function(id) { var dataFromCSharp = getDataFromDOM(id); var callbackParams = callMap[id]; delete callMap[id]; callbackParams.callback.apply(callbackParams.scope, [ dataFromCSharp.response, dataFromCSharp.success ]); }
On the C # side, we have a CallManager class that controls the subscription to calls from JavaScript. CallManager has a single handler for the WebKitBrowser.DocumentTitleChanged event, which deserializes the value of the
WebKitBrowser.DocumentTitle property and, depending on the name specified in JavaScript (config.name), calls the corresponding registered handler with the passed parameters. CallManager also takes into account the type of call: synchronous or asynchronous. Depending on the type, the handler is called either synchronously or asynchronously.
Web Inspector
We develop the interface in two stages. At the first stage, we develop it as a traditional web application using a browser and a web server. Only after achieving the desired result, we proceed to the second stage - packaging scripts and integration with C #.
At the first stage, we actively used developer tools in the browser and had full control over dom and JavaScript. But after the transition to the second stage, the entire interface simply turned into a black box. Problems with layout and JavaScript became difficult to detect. We were forced to look for at least some kind of web inspector.
Unfortunately, WebKit .NET does not allow the use of the native WebKit web inspector and no remote debugging is supported here. Therefore, we decided to use
Firebug Lite (1.4.0), an embedded debugger based on
Firebug .
In the HTML page of Firebug Lite we connect as follows:
<!DOCTYPE html> <html> <head> ... <script type='text/javascript' src='/path/to/firebug-lite.js'> { overrideConsole: true, startInNewWindow: true, startOpened: true, enableTrace: true } </script> ... </head> <body>...</body> </html>
When working with the embedded browser, it is more convenient when Firebug Lite opens in a new window, which is set by the startInNewWindow option. But in order for this to happen, some manipulations in C # are needed:
browser.Url = new Uri( "http://localhost/path/to/debug-version-of-interface.html", System.UriKind.Absolute); browser.NewWindowCreated += new NewWindowCreatedEventHandler( (sender, newWindowEventArgs) => {
Of course, Firebug Lite does not support debugging of scripts, but it gives the opportunity to explore the dom and gives us a JavaScript console, and this already facilitates development.
Conclusion
After all the improvements described above, WebKit .NET has become a completely usable embedded browser that works stably and copes with a fairly large dom.
Of course, the implementation of the interface in this way is associated with certain difficulties, which are mostly caused by the absence of a full-fledged web inspector, but there are also advantages. For example, you can reuse JavaScript code from other parts of the application, or even implement the same interface in a mobile, desktop and web application. Therefore, efforts can be considered justified.