QQmlBinding::QQmlBinding(const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) : QQmlJavaScriptExpression(&QQmlBinding_jsvtable), QQmlAbstractBinding(Binding) { if (ctxt && !ctxt->isValid()) return; const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) return; QString url; QV4::Function *runtimeFunction = 0; QQmlContextData *ctxtdata = QQmlContextData::get(scriptPrivate->context); QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine()); if (engine && ctxtdata && !ctxtdata->url.isEmpty() && ctxtdata->typeCompilationUnit) { url = ctxtdata->url.toString(); if (scriptPrivate->bindingId != QQmlBinding::Invalid) runtimeFunction = ctxtdata->typeCompilationUnit->runtimeFunctions.at(scriptPrivate->bindingId); } setNotifyOnValueChanged(true); QQmlAbstractExpression::setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context)); setScopeObject(obj ? obj : scriptPrivate->scope); if (runtimeFunction) { v4function = QV4::QmlBindingWrapper::createQmlCallableForFunction(ctxtdata, scopeObject(), runtimeFunction); } else { QString code = scriptPrivate->script; v4function = qmlBinding(context(), scopeObject(), code, url, scriptPrivate->lineNumber); } }
QT_BEGIN_NAMESPACE QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scope, const QString &expression, const QString &fileName, quint16 line, quint16 column, const QString &handlerName, const QString ¶meterString) : QQmlJavaScriptExpression(), m_index(index), m_target(target) { init(ctxt, scope); QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine()); QV4::ExecutionEngine *v4 = ep->v4engine(); QString function; // Add some leading whitespace to account for the binding's column offset. // It's 2 off because a, we start counting at 1 and b, the '(' below is not counted. function.fill(QChar(QChar::Space), qMax(column, (quint16)2) - 2); function += QStringLiteral("(function "); function += handlerName; function += QLatin1Char('('); if (parameterString.isEmpty()) { QString error; //TODO: look at using the property cache here (as in the compiler) // for further optimization QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); function += QQmlPropertyCache::signalParameterStringForJS(v4, signal.parameterNames(), &error); if (!error.isEmpty()) { qmlInfo(scopeObject()) << error; return; } } else function += parameterString; function += QStringLiteral(") { "); function += expression; function += QStringLiteral(" })"); m_function.set(v4, evalFunction(context(), scopeObject(), function, fileName, line)); if (m_function.isNullOrUndefined()) return; // could not evaluate function. Not valid. }
// Must be called with a valid handle scope QV4::ReturnedValue QQmlExpressionPrivate::v4value(bool *isUndefined) { Q_Q(QQmlExpression); if (!expressionFunctionValid) { function = qmlBinding(context(), scopeObject(), expression, url, line, &qmlscope); expressionFunctionValid = true; } QV4::ExecutionEngine *v4 = QQmlEnginePrivate::get(q->engine())->v4engine(); QV4::Scope scope(v4); QV4::ScopedValue f(scope, function.value()); return evaluate(context(), f, isUndefined); }
QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scope, QV4::Function *runtimeFunction) : QQmlJavaScriptExpression(), m_index(index), m_target(target) { // It's important to call init first, because m_index gets remapped in case of cloned signals. init(ctxt, scope); QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); QString error; QV4::ExecutionEngine *engine = QQmlEnginePrivate::getV4Engine(ctxt->engine); m_function.set(engine, QV4::FunctionObject::createQmlFunction(ctxt, scope, runtimeFunction, signal.parameterNames(), &error)); if (!error.isEmpty()) { qmlInfo(scopeObject()) << error; m_function.clear(); } }
QQmlBinding::QQmlBinding(const QQmlScriptString &script, QObject *obj, QQmlContext *ctxt) : QQmlJavaScriptExpression(&QQmlBinding_jsvtable), QQmlAbstractBinding(Binding) { if (ctxt && !ctxt->isValid()) return; const QQmlScriptStringPrivate *scriptPrivate = script.d.data(); if (!ctxt && (!scriptPrivate->context || !scriptPrivate->context->isValid())) return; bool needRewrite = true; QString code; int id = scriptPrivate->bindingId; if (id >= 0) { QQmlContextData *ctxtdata = QQmlContextData::get(scriptPrivate->context); QQmlEnginePrivate *engine = QQmlEnginePrivate::get(scriptPrivate->context->engine()); if (engine && ctxtdata && !ctxtdata->url.isEmpty()) { QQmlTypeData *typeData = engine->typeLoader.getType(ctxtdata->url); Q_ASSERT(typeData); if (QQmlCompiledData *cdata = typeData->compiledData()) { needRewrite = false; code = cdata->primitives.at(id); m_url = cdata->name; } typeData->release(); } } if (needRewrite) { QQmlRewrite::RewriteBinding rewriteBinding; code = rewriteBinding(scriptPrivate->script); } setNotifyOnValueChanged(true); QQmlAbstractExpression::setContext(QQmlContextData::get(ctxt ? ctxt : scriptPrivate->context)); setScopeObject(obj ? obj : scriptPrivate->scope); m_expression = scriptPrivate->script.toUtf8(); m_lineNumber = scriptPrivate->lineNumber; m_columnNumber = scriptPrivate->columnNumber; v8function = evalFunction(context(), scopeObject(), code, QString(), m_lineNumber); }
QQmlBoundSignalExpression::QQmlBoundSignalExpression(QObject *target, int index, QQmlContextData *ctxt, QObject *scope, QV4::Function *runtimeFunction) : QQmlJavaScriptExpression(&QQmlBoundSignalExpression_jsvtable), m_target(target), m_index(index), m_extra(0) { setExpressionFunctionValid(true); setInvalidParameterName(false); // It's important to call init first, because m_index gets remapped in case of cloned signals. init(ctxt, scope); QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); QString error; m_v8function = QV4::QmlBindingWrapper::createQmlCallableForFunction(ctxt, scope, runtimeFunction, signal.parameterNames(), &error); if (!error.isEmpty()) { qmlInfo(scopeObject()) << error; setInvalidParameterName(true); } else setInvalidParameterName(false); }
QV4::ReturnedValue QQmlJavaScriptExpression::evaluate(QQmlContextData *context, const QV4::ValueRef function, QV4::CallData *callData, bool *isUndefined) { Q_ASSERT(context && context->engine); if (function->isUndefined()) { if (isUndefined) *isUndefined = true; return QV4::Encode::undefined(); } QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context->engine); // All code that follows must check with watcher before it accesses data members // incase we have been deleted. DeleteWatcher watcher(this); Q_ASSERT(notifyOnValueChanged() || activeGuards.isEmpty()); GuardCapture capture(context->engine, this, &watcher); QQmlEnginePrivate::PropertyCapture *lastPropertyCapture = ep->propertyCapture; ep->propertyCapture = notifyOnValueChanged()?&capture:0; if (notifyOnValueChanged()) capture.guards.copyAndClearPrepend(activeGuards); QV4::ExecutionEngine *v4 = QV8Engine::getV4(ep->v8engine()); QV4::Scope scope(v4); QV4::ScopedValue result(scope, QV4::Primitive::undefinedValue()); QV4::ExecutionContext *ctx = v4->currentContext(); callData->thisObject = v4->globalObject; if (scopeObject()) { QV4::ScopedValue value(scope, QV4::QObjectWrapper::wrap(ctx->d()->engine, scopeObject())); if (value->isObject()) callData->thisObject = value; } result = function->asFunctionObject()->call(callData); if (scope.hasException()) { if (watcher.wasDeleted()) ctx->catchException(); // ignore exception else delayedError()->catchJavaScriptException(ctx); if (isUndefined) *isUndefined = true; } else { if (isUndefined) *isUndefined = result->isUndefined(); if (!watcher.wasDeleted() && hasDelayedError()) delayedError()->clearError(); } if (capture.errorString) { for (int ii = 0; ii < capture.errorString->count(); ++ii) qWarning("%s", qPrintable(capture.errorString->at(ii))); delete capture.errorString; capture.errorString = 0; } while (Guard *g = capture.guards.takeFirst()) g->Delete(); ep->propertyCapture = lastPropertyCapture; return result.asReturnedValue(); }
// Parts of this function mirror code in QQmlExpressionPrivate::value() and v8value(). // Changes made here may need to be made there and vice versa. void QQmlBoundSignalExpression::evaluate(void **a) { Q_ASSERT (context() && engine()); if (invalidParameterName()) return; QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine()); QV4::Scope scope(ep->v4engine()); ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation. { if (!expressionFunctionValid()) { Q_ASSERT(!m_extra.isNull()); QString expression; // Add some leading whitespace to account for the binding's column offset. // It's 2 off because a, we start counting at 1 and b, the '(' below is not counted. expression.fill(QChar(QChar::Space), qMax(m_extra->m_sourceLocation.column, (quint16)2) - 2); expression += QStringLiteral("(function "); expression += m_extra->m_handlerName; expression += QLatin1Char('('); if (m_extra->m_parameterString.isEmpty()) { QString error; //TODO: look at using the property cache here (as in the compiler) // for further optimization QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); expression += QQmlPropertyCache::signalParameterStringForJS(engine(), signal.parameterNames(), &error); if (!error.isEmpty()) { qmlInfo(scopeObject()) << error; setInvalidParameterName(true); ep->dereferenceScarceResources(); return; } } else expression += m_extra->m_parameterString; expression += QStringLiteral(") { "); expression += m_extra->m_expression; expression += QStringLiteral(" })"); m_extra->m_expression.clear(); m_extra->m_handlerName.clear(); m_extra->m_parameterString.clear(); m_v8function = evalFunction(context(), scopeObject(), expression, m_extra->m_sourceLocation.sourceFile, m_extra->m_sourceLocation.line, &m_extra->m_v8qmlscope); if (m_v8function.isNullOrUndefined()) { ep->dereferenceScarceResources(); return; // could not evaluate function. Not valid. } setExpressionFunctionValid(true); } QV8Engine *engine = ep->v8engine(); QVarLengthArray<int, 9> dummy; //TODO: lookup via signal index rather than method index as an optimization int methodIndex = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).methodIndex(); int *argsTypes = QQmlPropertyCache::methodParameterTypes(m_target, methodIndex, dummy, 0); int argCount = argsTypes ? *argsTypes : 0; QV4::ScopedValue f(scope, m_v8function.value()); QV4::ScopedCallData callData(scope, argCount); for (int ii = 0; ii < argCount; ++ii) { int type = argsTypes[ii + 1]; //### ideally we would use metaTypeToJS, however it currently gives different results // for several cases (such as QVariant type and QObject-derived types) //args[ii] = engine->metaTypeToJS(type, a[ii + 1]); if (type == QMetaType::QVariant) { callData->args[ii] = engine->fromVariant(*((QVariant *)a[ii + 1])); } else if (type == QMetaType::Int) { //### optimization. Can go away if we switch to metaTypeToJS, or be expanded otherwise callData->args[ii] = QV4::Primitive::fromInt32(*reinterpret_cast<const int*>(a[ii + 1])); } else if (type == qMetaTypeId<QQmlV4Handle>()) { callData->args[ii] = *reinterpret_cast<QQmlV4Handle *>(a[ii + 1]); } else if (ep->isQObject(type)) { if (!*reinterpret_cast<void* const *>(a[ii + 1])) callData->args[ii] = QV4::Primitive::nullValue(); else callData->args[ii] = QV4::QObjectWrapper::wrap(ep->v4engine(), *reinterpret_cast<QObject* const *>(a[ii + 1])); } else { callData->args[ii] = engine->fromVariant(QVariant(type, a[ii + 1])); } } QQmlJavaScriptExpression::evaluate(context(), f, callData, 0); } ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete. }
// Parts of this function mirror code in QQmlExpressionPrivate::value() and v8value(). // Changes made here may need to be made there and vice versa. void QQmlBoundSignalExpression::evaluate(void **a) { Q_ASSERT (context() && engine()); if (m_invalidParameterName) return; QQmlEnginePrivate *ep = QQmlEnginePrivate::get(engine()); ep->referenceScarceResources(); // "hold" scarce resources in memory during evaluation. { v8::HandleScope handle_scope; v8::Context::Scope context_scope(ep->v8engine()->context()); if (!m_expressionFunctionValid) { //TODO: look at using the property cache here (as in the compiler) // for further optimization QMetaMethod signal = QMetaObjectPrivate::signal(m_target->metaObject(), m_index); QQmlRewrite::RewriteSignalHandler rewriter; QString expression; bool ok = true; if (m_expressionFunctionRewritten) { expression = QString::fromUtf8(m_expressionUtf8); //if we need parameters, and the rewrite doesn't include them, //create and insert the parameter string now if (m_parameterCountForJS == -1 && signal.parameterCount()) { const QString ¶meters = rewriter.createParameterString(signal.parameterNames(), ep->v8engine()->illegalNames()); int index = expression.indexOf(QLatin1Char('('), 1); Q_ASSERT(index > -1); expression.insert(index + 1, parameters); setParameterCountForJS(rewriter.parameterCountForJS()); } m_expressionUtf8.clear(); } else { //expression is still in its original form, so perform a full rewrite expression = rewriter(m_expression, QString()/*no name hint available*/, &ok, signal.parameterNames(), ep->v8engine()->illegalNames()); setParameterCountForJS(rewriter.parameterCountForJS()); m_expression.clear(); } if (rewriter.hasParameterError()) { qmlInfo(scopeObject()) << rewriter.parameterError(); m_invalidParameterName = true; ep->dereferenceScarceResources(); return; } if (ok) { m_v8function = evalFunction(context(), scopeObject(), expression, m_fileName, m_line, &m_v8qmlscope); } if (m_v8function.IsEmpty() || m_v8function->IsNull()) { ep->dereferenceScarceResources(); return; // could not evaluate function. Not valid. } setUseSharedContext(false); m_expressionFunctionValid = true; } if (!hasParameterInfo()) { QQmlJavaScriptExpression::evaluate(context(), m_v8function, 0); } else { QV8Engine *engine = ep->v8engine(); QVarLengthArray<int, 9> dummy; //TODO: lookup via signal index rather than method index as an optimization int methodIndex = QMetaObjectPrivate::signal(m_target->metaObject(), m_index).methodIndex(); int *argsTypes = QQmlPropertyCache::methodParameterTypes(m_target, methodIndex, dummy, 0); int argCount = argsTypes ? m_parameterCountForJS : 0; QVarLengthArray<v8::Handle<v8::Value>, 9> args(argCount); for (int ii = 0; ii < argCount; ++ii) { int type = argsTypes[ii + 1]; //### ideally we would use metaTypeToJS, however it currently gives different results // for several cases (such as QVariant type and QObject-derived types) //args[ii] = engine->metaTypeToJS(type, a[ii + 1]); if (type == QMetaType::QVariant) { args[ii] = engine->fromVariant(*((QVariant *)a[ii + 1])); } else if (type == QMetaType::Int) { //### optimization. Can go away if we switch to metaTypeToJS, or be expanded otherwise args[ii] = v8::Integer::New(*reinterpret_cast<const int*>(a[ii + 1])); } else if (type == qMetaTypeId<QQmlV8Handle>()) { args[ii] = reinterpret_cast<QQmlV8Handle *>(a[ii + 1])->toHandle(); } else if (ep->isQObject(type)) { if (!*reinterpret_cast<void* const *>(a[ii + 1])) args[ii] = v8::Null(); else args[ii] = engine->newQObject(*reinterpret_cast<QObject* const *>(a[ii + 1])); } else { args[ii] = engine->fromVariant(QVariant(type, a[ii + 1])); } } QQmlJavaScriptExpression::evaluate(context(), m_v8function, argCount, args.data(), 0); } } ep->dereferenceScarceResources(); // "release" scarce resources if top-level expression evaluation is complete. }
v8::Local<v8::Value> QQmlJavaScriptExpression::evaluate(QQmlContextData *context, v8::Handle<v8::Function> function, int argc, v8::Handle<v8::Value> args[], bool *isUndefined) { Q_ASSERT(context && context->engine); if (function.IsEmpty() || function->IsUndefined()) { if (isUndefined) *isUndefined = true; return v8::Local<v8::Value>(); } QQmlEnginePrivate *ep = QQmlEnginePrivate::get(context->engine); Q_ASSERT(notifyOnValueChanged() || activeGuards.isEmpty()); GuardCapture capture(context->engine, this); QQmlEnginePrivate::PropertyCapture *lastPropertyCapture = ep->propertyCapture; ep->propertyCapture = notifyOnValueChanged()?&capture:0; if (notifyOnValueChanged()) capture.guards.copyAndClearPrepend(activeGuards); QQmlContextData *lastSharedContext = 0; QObject *lastSharedScope = 0; bool sharedContext = useSharedContext(); // All code that follows must check with watcher before it accesses data members // incase we have been deleted. DeleteWatcher watcher(this); if (sharedContext) { lastSharedContext = ep->sharedContext; lastSharedScope = ep->sharedScope; ep->sharedContext = context; ep->sharedScope = scopeObject(); } v8::Local<v8::Value> result; { v8::TryCatch try_catch; v8::Handle<v8::Object> This = ep->v8engine()->global(); if (scopeObject() && requiresThisObject()) { v8::Handle<v8::Value> value = ep->v8engine()->newQObject(scopeObject()); if (value->IsObject()) This = v8::Handle<v8::Object>::Cast(value); } result = function->Call(This, argc, args); if (isUndefined) *isUndefined = try_catch.HasCaught() || result->IsUndefined(); if (watcher.wasDeleted()) { } else if (try_catch.HasCaught()) { v8::Context::Scope scope(ep->v8engine()->context()); v8::Local<v8::Message> message = try_catch.Message(); if (!message.IsEmpty()) { delayedError()->setMessage(message); } else { if (hasDelayedError()) delayedError()->clearError(); } } else { if (hasDelayedError()) delayedError()->clearError(); } } if (sharedContext) { ep->sharedContext = lastSharedContext; ep->sharedScope = lastSharedScope; } if (capture.errorString) { for (int ii = 0; ii < capture.errorString->count(); ++ii) qWarning("%s", qPrintable(capture.errorString->at(ii))); delete capture.errorString; capture.errorString = 0; } while (Guard *g = capture.guards.takeFirst()) g->Delete(); ep->propertyCapture = lastPropertyCapture; return result; }