The latest version of Nuxeo Drive, our desktop synchronization included a new HTML5 UI and embedded WebKit, providing more reactivity, consistency between operating systems and a nicer look and feel. This UI uses a specific drive object to communicate with the core software written in Python and QT. Here’s an in-depth look into how we created that drive object, using the QtWebKit module, a web content rendering engine based on the open source WebKit project.
We created a QWebView object and then attached an API object that will expose different functions to the JavaScript engine.
addToJavaScriptWindowObject("drive", self._api)
The API object is basically a QObject with slots that will be exposed on the JavaScript side (slots and signals are by default exposed to JavaScript engine)
@QtCore.pyqtSlot(str, str, result=str)
def update_password(self, uid, password):
return self._update_password(uid, password)
Everything was great, but there was one challenge! Some functions, such as update_password, can take a little longer than what we wanted, as it performs network calls. We, as AngularJS developers, love our $q service and the promise system. Unfortunately, the WebKit JavaScript engine runs in the main thread so the slots are called in the same thread and the UI freezes while accessing the network or if Python processes for too long. For users this could be annoying as they would never know if they clicked on the button or not. So, this method wasn’t good enough.
Promise Emulation
To overcome this challenge, we came up with an idea: If the method would return a QObject Promise object, the JavaScript engine could then use this object, connecting its return signals to its own slots and running the encapsulated thread in this Promise object. The slots and signals will allow inter-thread communication which will prevent the UI from freezing.
The code showing our Promise object implementation is available on Gist.
The Worker is already an object that encapsulates a thread. This way you can simply modify your API object like this (see: drive_api.py):
`@QtCore.pyqtSlot(str, str, result=QtCore.QObject) def update_password_async(self, uid, password): return Promise(self._update_password, uid, password)`
Now this will return a Promise object. On the JavaScript side we had to create a handler that will, in case of a Promise object, create the slots and signals connection and run the Promise and in any other case will forward the result (see: drive.js)
So now the sequence is like this:
Our Promise looks good and works well except for the “small” log we get whenever we use it:
QObject: Cannot create children for a parent that is in a different thread.
(Parent is Promise(0x1753220), parent's thread is QThread(0x1766d40), current thread is QThread(0x1049240)
The issue is that Qt is wrapping the exposed QObject with another internal object, and while doing that it calls the reparent to attach the exposed object. But the QObject was already moved to another thread by the Worker mechanism. We had to solve this by using a wrapper: PromiseWrapper. This is the object that will be hosted in the Promise thread. The Promise object can then stay in the main thread (see: drive_api_final.py).
This small line of log disappeared now! (It was, by the way, killing the application under Linux. So thanks to the bug reporter :) )
Nuxeo Drive can now display a better animated button when something is happening in the background after a user interaction.