CopperSpice API  1.9.2
CsWebKit Bridge

The CsWebKit Bridge is a mechanism to allow code running in the JavaScript sandbox to access native C++ objects represented as QObjects. The bridge takes advantage of the CopperSpice Meta Object System. For example, QObject properties map directly to JavaScript properties.

Use Cases

There are two main use cases for the CsWebKit bridge: (1) web content in native applications and (2) thin clients.

Web Content in Native Applications

As an example, consider an application which contains a media-player, playlist manager, and a music store.

The playlist manager is best implemented as a desktop application with QWidgets as the application's backbone. The media-player control usually has a custom look and feel and is best written using the Graphics View system or Declarative.

The music store shows dynamic content and is best authored in HTML and maintained on a server. With the CsWebKit bridge the music store component can interact with native parts of the application, for example, when a file needs to be saved to a specific location.

Thin Clients

This case is a native backend of a full web application, referred to as a thin client. The UI is driven by HTML, JavaScript and CSS. Additionally, it uses CopperSpice components to access native features usually not exposed to the web or to enable helper components which are usually written in C++.

An example for such a client is a UI for a video-on-demand service on a TV. The entire content and UI can be kept on the server, served dynamically through HTTP and rendered with WebKit. Additional native components are used to access hardware-specific features like extracting a list of images out of a video stream.

Difference from Other Bridge Technologies

CsWebKit is not the only bridge technology. NPAPI, for example, is a long-time standard for web-native bridging. Due to the CopperSpice meta object system, full applications leveraging web technologies are much easier to develop with the CsWebKit bridge than with NPAPI. NPAPI, however, is better for cross-browser plugins, due to it being an accepted standard.

When developing a plugin for a browser, NPAPI is recommended. When developing a full application utilizing HTML-rendering, the CsWebKit bridge is recommended.

Relationship with CsScript

CsWebKit bridge is similar to CsScript, especially for some of the features described in the Making Applications Scriptable. CopperSpice does not provide the full CsScript API for web applications.

Making QObjects known to JavaScript via QWebFrame

By default, no QObjects are accessible through the web environment, for security reasons. When a web application wants to access a native QObject, it must explicitly grant access to this QObject, using the following call.

QWebFrame *frame = myWebPage->mainFrame();
frame->addToJavaScriptWindowObject("someNameForMyObject", myObject);

Refer to QWebFrame::addToJavaScriptWindowObject() for more information.

Using Signals and Slots

The CsWebKit bridge adapts the CopperSpice central Signals and Slots feature for scripting. There are three principal ways to use signals and slots with the CsWebKit bridge.

  • Hybrid C++/script: C++ application code connects a signal to a script function. This approach is useful if you have a QObject but do not want to expose the object itself to the scripting environment. You just want to define how the script responds to a signal and leave it up to the C++ side of your application to establish the connection between the C++ signal and the JavaScript slot.
  • Hybrid script/C++: A script can connect signals and slots to establish connections between pre-defined objects that the application exposes to the scripting environment. In this scenario, the slots themselves are still written in C++, but the definition of the connections is fully dynamic (script-defined).
  • Purely script-defined: A script can both define signal handler functions (effectively "slots written in JavaScript"), and set up the connections that utilize those handlers. For example, a script can define a function that will handle the QLineEdit::returnPressed() signal, and then connect that signal to the script function.

CsScript functions such as qScriptConnect are unavailable in the web environment.

Signal to Function Connections

function myInterestingScriptFunction() {
// ...
}
myQObject.somethingChanged.connect(myInterestingScriptFunction);

The call to connect() establishes a connection between the signal somethingChanged and the slot myInterestingScriptFunction. Whenever the object myObject emits the signal somethingChanged, the slot myInterestingScriptFunction gets called automatically.

The argument of connect() can be any JavaScript function as in the above example or a slot of a QObject as in the following example.

myQObject.somethingChanged.connect(myOtherQObject.doSomething);

When the argument is a slot of a QObject, the argument types of the signal and the slot do not have to be compatible. If possible, the CsWebKit bridge converts the signal arguments such that they match the slot argument.

To disconnect a slot from a signal, call the method disconnect() on the signal with the slot as its argument.

myQObject.somethingChanged.disconnect(myInterestingFunction);
myQObject.somethingChanged.disconnect(myOtherQObject.doSomething);

When a script function is invoked in response to a signal, the this object will be the Global Object.

Signal to Member Function Connections

myQObject.somethingChanged.connect(thisObject, function)

The call to connect() establishes a connection between the signal somethingChanged and the slot function. Whenever the object myObject emits the signal the slot of the object thisObject is called.

If you have a push button in a form you typically want the form to do something in response to the button's clicked signal. The call to connect() ensures the method onClicked() is called when the user clicks the push button. The slot onClicked() prints the value of x as stored in the form.

var form = { x: 123 };
var onClicked = function() { print(this.x); };
myButton.clicked.connect(form, onClicked);

To disconnect a slot from a signal, you pass the same arguments to disconnect() as you passed to connect().

myQObject.somethingChanged.disconnect(thisObject, function);

Signal to Named Member Function Connections

myQObject.somethingChanged.connect(thisObject, "functionName")

This form of the connect() function requires that the first argument thisObject is the object that will be bound to this when the function functionName is invoked in response to the signal somethingChanged. The second argument functionName specifies the name of a function that is connected to the signal. It refers to a member function of the object thisObject.

The function is resolved when the connection is made, not when the signal is emitted.

var obj = { x: 123, fun: function() { print(this.x); } };
myQObject.somethingChanged.connect(obj, "fun");

To disconnect from the signal, pass the same arguments to disconnect() as you passed to connect.

myQObject.somethingChanged.disconnect(thisObject, "functionName");

Error Handling

When a call to connect() or disconnect() succeeds the value undefined will be returned. Otherwise, it will throw a script exception. The error message can be obtained from the resulting Error object.

try {
myQObject.somethingChanged.connect(myQObject, "slotWhichDoesNotExist");
} catch (e) {
print(e);
}

Emitting Signals from Scripts

To emit a signal from script code invoke the signal passing the relevant arguments. It is currently not possible to define a new signal in a script, all signals must be defined by C++ classes.

myQObject.somethingChanged("hello");

Overloaded Signals and Slots

When a signal or slot is overloaded, the CsWebKit bridge will attempt to pick the right overload based on the actual types of the QScriptValue arguments involved in the function invocation. For example, if your class has slots myOverloadedSlot(int) and myOverloadedSlot(QString), the following script code will behave reasonably.

myQObject.myOverloadedSlot(10); // calls the int overload
myQObject.myOverloadedSlot("10"); // calls the QString overload

You can specify a particular overload by using array style property access with the normalized signature of the C++ method as the property name.

myQObject['myOverloadedSlot(int)']("10"); // calls int overload, argument is converted to an int
myQObject['myOverloadedSlot(QString)'](10); // calls QString overload, argument is converted to a string

If the overloads have different number of arguments, the CsWebKit bridge will pick the overload with the argument count that best matches the actual number of arguments passed to the slot. For overloaded signals, JavaScript will throw an error if you try to connect to the signal by name; you have to refer to the signal with the full normalized signature of the particular overload you want to connect to.

Invokable Methods

Both slots and signals are invokable from scripts by default. In addition, it is also possible to define a method that is invokable from scripts, although the method is neither a signal nor a slot. This is especially useful for functions with return types, as slots normally do not return anything (it would be meaningless to return a value from a slot, as the connected signals can not handle return values). To make a non-slot method invokable, simply add the CS_INVOKABLE() macro before its definition.

class MyObject : public QObject
{
CS_OBJECT(MyObject)
public:
CS_INVOKABLE_METHOD_1(Public, void thisMethodIsInvokableInJavaScript())
CS_INVOKABLE_METHOD_2(thisMethodIsInvokableInJavaScript)
void thisMethodIsNotInvokableInJavaScript();
};

Accessing Properties

The properties of a QObject are available as properties of the corresponding JavaScript object. When you manipulate a property in script code, the C++ get/set method for that property will automatically be invoked. For example, if your C++ class has a property declared as follows.

CS_PROPERTY_READ(enabled, enabled)
CS_PROPERTY_WRITE(enabled, setEnabled)

Then script code is called as shown below.

myQObject.enabled = true;
// ...
myQObject.enabled = ! myQObject.enabled;

Accessing Child QObjects

Every named child of a QObject (that is, every child for which QObject::objectName() does not return the empty string) is by default available as a property of the JavaScript wrapper object. For example, if you have a QDialog with a child widget whose objectName property is "okButton", you can access this object in script code through the expression.

myDialog.okButton

Because objectName is a property you can modify the name in script code to rename an object.

myDialog.okButton
myDialog.okButton.objectName = "cancelButton";
// from now on, myDialog.cancelButton references the button

Data types

When calling slots, receiving signals or accessing properties, usually some payload is involved. For example, a property "text" might return a QString parameter. The CsWebKit bridge does the job of converting between a given JavaScript data-type, and the expected or given CopperSpice type. Each CopperSpice type has a corresponding set of rules of how JavaScript treats it.

The data type conversions are also applicable for the data returned from non-void invokable methods.

Numbers

All CopperSpice numeric data types are converted to or from a JavaScript number. These include int, short, float, double, and the portable CopperSpice types (qreal, qint etc). A special case is QChar. If a slot expects a QChar the CsWebKit bridge uses the Unicode value in case of a number and the first character in case of a string.

Non-standard number types are not automatically converted to or from a JavaScript number. Instead standard number types should be used for signals, slots, and properties.

When a non-number is passed as an argument to a method or property that expects a number, the appropriate JavaScript conversion function (parseInt / parseFloat) is used.

Strings

When JavaScript accesses methods or properties that expect a QString, the CsWebKit bridge will automatically convert the value to a string (if it is not already a string), using the built-in JavaScript toString method. When a QString is passed to JavaScript from a signal or a property, the CsWebKit bridge converts it into a JavaScript string.

Date & Time

Both QDate, QTime and QDateTime are automatically translated to or from the JavaScript Date object. If a number is passed as an argument to a method that expects one of the date/time types, the CsWebKit bridge treats it as a timestamp. If a string is passed, CsWebKit tries the different CopperSpice date parsing functions to perform the right translation.

Regular Expressions

The CsWebKit bridge automatically converts a JavaScript regular expression object to a QRegularExpression. If a string is passed to a method expecting a QRegularExpression, the string is converted to a QRegularExpression.

Lists

The CsWebKit bridge supports the following list data types: QVariantList, QStringList, QObjectList and QList<int>. When a slot or property expects one of those list types, the CsWebKit bridge tries to convert a JavaScript array into that type, converting each of the array's elements to the single-element type of the list.

The most useful type of list is QVariantList, which can be converted to and from any JavaScript array.

Compound (JSON) objects

JavaScript compound objects, also known as JSON objects, are variables that hold a list of key-value pairs, where all the keys are strings and the values can have any type. This translates very well to QVariantMap, which is nothing more than a QMap from QString to QVariant.

The seamless conversion between JSON objects and QVariantMap allows for a very convenient way of passing arbitrary structured data between C++ and the JavaScript environment. If the native QObject makes sure that compound values are converted to QVariantMaps and QVariantLists, JavaScript is guaranteed to receive them in a meaningful way.

Types that are not supported by JSON, such as JavaScript functions and getters/setters, are not converted.

QVariants

When a slot or property accepts a QVariant, the CsWebKit bridge creates a QVariant that best matches the argument passed by JavaScript. A string, for example, becomes a QVariant holding a QString, a normal JSON object becomes a QVariantMap, and a JavaScript array becomes a QVariantList.

QObjects

Pointers to a QObject or a QWidget can be used in signals, slots and properties. This object can then be used like an object that is exposed directly. Its slots can be invoked, its signals connected to, etc. However, this functionality is fairly limited - the type used has to be QObject* or QWidget*. If the type specified is a pointer to a non-QWidget subclass of QObject, the CsWebKit bridge does not recognize it as a QObject.

In general its advised to use care when passing QObjects as arguments, as those objects do not become owned by the JavaScript engine; That means that the application developer has to be extra careful not to try to access QObjects that have already been deleted by the native environment.

Pixmaps and Images

The CsWebKit bridge handles QPixmaps and QImages in a special way. Since CsWebKit stores QPixmaps to represent HTML images, QPixmaps coming from the native environment can be used directly inside WebKit. A QImage or a QPixmap coming from CopperSpice is converted to an intermediate JavaScript object, which can be represented as shown.

{
width: ...,
height: ...,
toDataURL: function() { ... },
assignToHTMLImageElement: function(element) { ... }
}

The JavaScript environment can then use the pixmap from CopperSpice and display it inside the HTML environment, by assigning it to an existing <img> element with assignToHTMLImageElement(). It can also use the toDataURL() function, which allows using the pixmap as the src attribute of an image or as a background-image URL. The toDataURL() function is costly and should be used with caution.

C++ code:

class MyObject : QObject
{
CS_OBJECT(MyObject)
CS_PROPERTY_READ(myPixmap, getPixmap)
public:
QPixmap getPixmap() const;
};
MyObject myObject;
myWebPage.mainFrame()->addToJavaScriptWindowObject("myObject", &myObject);

HTML code:

<html>
<head>
<script>
function loadImage()
{
myObject.myPixmap.assignToHTMLImageElement(document.getElementById("imageElement"));
}
</script>
</head>
<body onload="loadImage()">
<img id="imageElement" width="300" height="200" />
</body>
</html>

When a CopperSpice object expects a QImage or a QPixmap as input, and the argument passed is an HTML image element, the CsWebKit bridge would convert the pixmap assigned to that image element into a QPixmap or a QImage.

QWebElement

A signal, slot or property that expects or returns a QWebElement can work seamlessly with JavaScript references to DOM elements. The JavaScript environment can select DOM elements, keep them in variables, then pass them to CopperSpice as a QWebElement, and receive them back.

C++ code:

class MyObject : public QObject
{
CS_OBJECT(MyObject)
public:
CS_SLOT_1(Public, void doSomethingWithWebElement(const QWebElement &element))
CS_SLOT_2(doSomethingWithWebElement)
};
MyObject myObject;
myWebPage.mainFrame()->addToJavaScriptWindowObject("myObject", &myObject);

HTML code:

<html>
<head>
<script>
function runExample()
{
myObject.doSomethingWithWebElement(document.getElementById("someElement"));
}
</script>
</head>
<body onload="runExample()">
<image id="someElement">Text</image>
</body>
</html>

This is specifically useful to create custom renderers or extensions to the web environment. Instead of forcing CopperSpice to select the element, the web environment selects the element and then sends the selected element directly to CopperSpice. QWebElements are not thread safe an object handling them has to live in the UI thread.

Limiting the Scope of the Hybrid Layer

When using the CsWebKit hybrid features it not advisable to make the API exposed to JavaScript use all its features. This leads to complexity and can create bugs that are hard to find. Instead, it is advisable to keep the hybrid layer small and manageable. Create a gate only when there's an actual need for it, i.e. there's a new native enabler that requires a direct interface to the application layer. Sometimes new functionality is better handled internally in the native layer or in the web layer; simplicity is your friend.

This usually becomes more apparent when the hybrid layer can create or destroy objects, or uses signals, slots or properties with a QObject* argument. It is advised to be very careful and to treat an exposed QObject as a system with careful attention to memory management and object ownership.

Internet Security

When exposing native objects to an open web environment, it is important to understand the security implications. Think whether the exposed object enables the web environment access things that should not be open, and whether the web content loaded by that web page comes from a trusted source. In general, when exposing native QObjects that give the web environment access to private information or to functionality that's potentially harmful to the client, such exposure should be balanced by limiting the web page's access to trusted URLs only with HTTPS, and by utilizing other measures as part of a security strategy.