Changes between Initial Version and Version 1 of QtScript

Jul 27, 2010 8:25:37 AM (12 years ago)



  • QtScript

    v1 v1  
     1= QtScript =
     3QtScript provides a Qt-ish (C++) API on top of the JavaScriptCore C API.
     4It is based on the existing QtScript module provided by Qt:
     6The work lives under JavaScriptCore/qt in webkit trunk.
     8 is the umbrella task for this project.
     10== QObject Binding ==
     12The purpose of the QObject binding, or bridge as it's called in QtWebKit, is to provide a JS wrapper object (AKA delegate) that enables access to a QObject from JavaScript.
     14The binding is completely dynamic, relying on QMetaObject introspection to figure out what properties and methods the C++ object has. Any QObject-derived class instance can be exported to JavaScript this way, without the C++ class author having to do anything special; just declare his properties (Q_PROPERTY) and slots in the class declaration.
     16The binding can roughly be divided into three "parts":
     17 * Handling property access.
     18 * Calling (meta-)methods.
     19 * Connections (C++ signal --> JS "slot").
     21The binding depends on QtScript's meta-type-based value conversion.
     23=== Binding Behavior ===
     25This section describes the current behavior of the QtScript binding in Qt. Not all of this is documented in the official docs (
     27A QObject wrapper handles the following:
     28 * Reading:
     29    * '''Static (QMetaObject) properties'''. Returns an accessor (get/set functions) for the property, not the actual value.
     30    * '''Dynamic (QObject::dynamicPropertyNames()) properties'''. Returns the property's value, converted from QVariant.
     31    * '''Meta-methods (signals, slots) by name'''. Returns a method wrapper object.
     32    * '''Meta-methods (signals, slots) by signature'''. Returns a method wrapper object.
     33    * '''Named child objects'''. Returns a wrapper object for the child.
     34 * Writing:
     35    * Static (QMetaObject) properties.
     36    * Dynamic properties.
     37    * Automatically creating a dynamic property when the property doesn't already exist.
     39The binding is completely dynamic. For example, if a dynamic property is added to the C++ object after the QtScript wrapper has been created, the wrapper can be used to access the new property. Conversely, if the dynamic property is removed after the QtScript wrapper has been created, the wrapper will no longer report the property. Similarly, if a child object is added, renamed or removed after the wrapper has been created, wrapper property access will reflect this.
     41=== Customizing Binding Behavior ===
     43The QScriptEngine::QObjectWrapOption flags can be used to configure the binding, per QObject wrapper. None of these options are on by default.
     45 * '''ExcludeChildObjects''': Don't expose child objects.
     46 * '''ExcludeSuperClass{Properties,Methods,Contents}''': Don't include members from the superclass (QMetaObject::superClass()).
     47 * '''QScriptEngine::ExcludeDeleteLater''': This is a very special case of excluding superclass content: Excludes the QObject::deleteLater() slot, so that a script can't delete the object.
     48 * '''QScriptEngine::AutoCreateDynamicProperties''': Create a new dynamic property on the C++ object, rather than on the JS wrapper object, when the property written doesn't exist.
     49 * '''QScriptEngine::PreferExistingWrapperObject''': Return an existing wrapper object if one has previously been created with the same configuration.
     50 * '''QScriptEngine::SkipMethodsInEnumeration''': Don't include methods (signals and slots) when enumerating the object's properties.
     51 * '''QScriptEngine::ExcludeSlots''': Don't expose slots.
     53Sensible wrap options that have been requested, but not implemented (yet):
     54 * Reads of non-existent properties should throw a ReferenceError, rather than returning undefined (like normal JS objects do).
     55 * Writes to non-existent properties should throw a ReferenceError, rather than silently creating a new property (like normal JS objects do).
     56 * Disable implicit type conversion; e.g. attempts to write a string to a property of type int, or pass an int to a method that expects a string, should throw a TypeError.
     58=== Property Attributes ===
     60 * '''Static (QMetaObject) properties''': DontDelete, ReadOnly if QMetaProperty::isWritable() returns false.
     61 * '''Dynamic (QObject::dynamicPropertyNames()) properties''': No attributes. (Dynamic properties can be changed and deleted!)
     62 * '''Meta-methods (by name or signature)''': No attributes. Slots can be replaced by any value. Slots appear to be deletable (the delete operator will return true), but will resurface on subsequent reads.
     63 * '''Named child objects''': DontDelete, ReadOnly, DontEnum. There's no way to enumerate child objects. The delete operator returns true due to a bug since 4.6; the child is not actually deleted.
     65=== C++ Object Ownership ===
     67The QScriptEngine::ValueOwnership enum is used to specify what happens with the underlying C++ object when the JS wrapper object is garbage collected.
     69 * '''QScriptEngine::QtOwnership''': The object is owned by C++, i.e. will not be deleted. This is the default.
     70 * '''QScriptEngine::ScriptOwnership''': The object will be deleted.
     71 * '''QScriptEngine::AutoOwnership''': The object will only be deleted if it doesn't have a parent.
     73=== Calling Meta-Methods ===
     75The binding enables you to call QObject methods (typically slots, but also methods with the Q_INVOKABLE modifier) from JavaScript, like any other JS function.
     76A C++ method wrapper object does the job of invoking the corresponding method when the function is called from JS.
     78JS value arguments are converted to the types expected by the C++ method. If too few arguments are provided, a TypeError is thrown. If too many arguments are provided, the extra arguments are ignored (but may still be accessed by the slot if the class inherits QScriptable -- more on that later).
     79The C++ return value is converted to a JS value and passed back as the result from the method wrapper.
     81Like other C++ methods, meta-methods can be overloaded, and they can have default arguments. The QtScript binding tries to ensure that the intended overload is called. First, if there is an overload that expects precisely as many arguments as were passed, that overload is selected. Second, if there's more than one such overload, the decision is based on a heuristic of how well the source (JS) argument types match with the target (C++) argument types. If the heuristic doesn't help either, a TypeError is thrown.
     83=== Signal & Slot Connections ===
     85The binding provides a JavaScript "equivalent" of the C++ QObject::connect() function.
     87Every signal wrapper has functions connect() and disconnect(). Their usage is documented at
     88Internally, connect() and disconnect() delegate to a "connection manager" that acts as the middle-man, ensuring that when the C++ signal is emitted, the associated JS function ("signal handler") is called.
     90A signal is bound to the wrapper object it was retrieved from; this is necessary because when connect() is invoked, the JS this-object will be the signal, not the wrapper object. So while "button.clicked.connect(...)" is a very convenient syntax, it comes at a price; it's not possible to share the clicked() signal wrapper between instances.
     92One limitation of the QtScript signal & slot mechanism is that it doesn't provide a way to force a queued connection.
     94=== Handling QObject Deletion ===
     96It's possible that a wrapped QObject is deleted "behind our back", i.e. outside the QtScript environment.
     97To detect this, QtScript stores the QObject* in a QPointer. Trying to perform an operation (e.g. property access) on a wrapper object whose underlying C++ object has been deleted will cause an error to be thrown.
     99=== Implementation Concerns & Ideas ===
     101The Qt implementation of the QObject binding has some performance/memory issues, which we should try to avoid from the beginning in a new implementation. See
     103'''"Too dynamic"''': The Qt implementation doesn't do any work at wrapper creation time to figure out which properties the QObject has. All the work is done in "catch-all" access functions for the object. This means the VM has no chance of optimizing property access, e.g. by delegating directly to an accessor function for a static (known at JIT compile time) property.
     105An alternative would be to create a "JS class" per QMetaObject, and populate it with accessors for named (static) properties and slots. There would still need to be fallbacks (interceptors) to handle access to dynamic properties and child objects. Alternatively, wrap options could be introduced to treat dynamic properties and children as fixed properties too. A wilder idea is to listen for ChildAdded/ChildRemoved/DynamicPropertyChange and change the JS class on the fly.
     107Creating a JS class with named property accessors has the potential downside that a lot of accessors could be created that would never be used. Maybe a hybrid approach is possible, where named accessors are installed lazily by the catch-all accessor?
     109'''Slow lookup''': The Qt implementation converts the JS property name (JSC::Identifier) to a Latin-1 string, and uses that string to query the QMetaObject. Both these operations are slow. There should be a direct association between the JS property name (in whatever form the VM stores it) to the Qt property. The JS class approach outlined above seems like the preferred way to achieve that (let the VM do as much of the work as possible, avoid our own hashing/caching).
     111'''Slow method resolution''': First, the possibility of a meta-method being overloaded means that all the methods need to be searched (including the superclass methods -- it's possible that a subclass overloads one of the superclass's methods!), and with QMetaObject you can only do a linear search.
     113Second, in order to obtain the meta-types for the return type and arguments, some costly operations are performed: 1) The parameter types are extracted using QMetaMethod::parameterTypes(). 2) The type names are resolved to meta-type ids using QMetaType::type(). At least for built-in types (it's not safe for custom types because they can be unregistered), this information could be computed once and cached.