diff options
Diffstat (limited to 'src/plugins')
40 files changed, 2845 insertions, 446 deletions
diff --git a/src/plugins/plugins.pro b/src/plugins/plugins.pro index 0afd71767e..86fdf87650 100644 --- a/src/plugins/plugins.pro +++ b/src/plugins/plugins.pro @@ -1,5 +1,5 @@ TEMPLATE = subdirs QT_FOR_CONFIG += qml -qtConfig(qml-debug):SUBDIRS += qmltooling +qtConfig(thread):qtConfig(qml-debug):SUBDIRS += qmltooling qtHaveModule(quick):SUBDIRS += scenegraph diff --git a/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp b/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp index e1d6263e36..3e75e39f86 100644 --- a/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp +++ b/src/plugins/qmltooling/packetprotocol/qpacketprotocol.cpp @@ -106,6 +106,9 @@ class QPacketProtocolPrivate : public QObjectPrivate public: QPacketProtocolPrivate(QIODevice *dev); + bool writeToDevice(const char *bytes, qint64 size); + bool readFromDevice(char *buffer, qint64 size); + QList<qint32> sendingPackets; QList<QByteArray> packets; QByteArray inProgress; @@ -125,7 +128,6 @@ QPacketProtocol::QPacketProtocol(QIODevice *dev, QObject *parent) Q_ASSERT(dev); QObject::connect(dev, &QIODevice::readyRead, this, &QPacketProtocol::readyToRead); - QObject::connect(dev, &QIODevice::aboutToClose, this, &QPacketProtocol::aboutToClose); QObject::connect(dev, &QIODevice::bytesWritten, this, &QPacketProtocol::bytesWritten); } @@ -143,18 +145,18 @@ void QPacketProtocol::send(const QByteArray &data) return; // We don't send empty packets if (data.size() > maxSize) { - emit invalidPacket(); + emit error(); return; } - qint32 sendSize = data.size() + sizeof(qint32); + const qint32 sendSize = data.size() + static_cast<qint32>(sizeof(qint32)); d->sendingPackets.append(sendSize); + qint32 sendSizeLE = qToLittleEndian(sendSize); - qint64 writeBytes = d->dev->write((char *)&sendSizeLE, sizeof(qint32)); - Q_UNUSED(writeBytes); - Q_ASSERT(writeBytes == sizeof(qint32)); - writeBytes = d->dev->write(data); - Q_ASSERT(writeBytes == data.size()); + if (!d->writeToDevice((const char *)&sendSizeLE, sizeof(qint32)) + || !d->writeToDevice(data.data(), data.size())) { + emit error(); + } } /*! @@ -206,17 +208,6 @@ bool QPacketProtocol::waitForReadyRead(int msecs) } while (true); } -/*! - Return the QIODevice passed to the QPacketProtocol constructor. -*/ -void QPacketProtocol::aboutToClose() -{ - Q_D(QPacketProtocol); - d->inProgress.clear(); - d->sendingPackets.clear(); - d->inProgressSize = -1; -} - void QPacketProtocol::bytesWritten(qint64 bytes) { Q_D(QPacketProtocol); @@ -240,28 +231,40 @@ void QPacketProtocol::readyToRead() // Need to get trailing data if (-1 == d->inProgressSize) { // We need a size header of sizeof(qint32) - if (sizeof(qint32) > (uint)d->dev->bytesAvailable()) + if (static_cast<qint64>(sizeof(qint32)) > d->dev->bytesAvailable()) return; // Read size header qint32 inProgressSizeLE; - const qint64 read = d->dev->read((char *)&inProgressSizeLE, sizeof(qint32)); + if (!d->readFromDevice((char *)&inProgressSizeLE, sizeof(qint32))) { + emit error(); + return; + } d->inProgressSize = qFromLittleEndian(inProgressSizeLE); // Check sizing constraints - if (read != sizeof(qint32) || d->inProgressSize < read) { + if (d->inProgressSize < qint32(sizeof(qint32))) { disconnect(d->dev, &QIODevice::readyRead, this, &QPacketProtocol::readyToRead); - disconnect(d->dev, &QIODevice::aboutToClose, this, &QPacketProtocol::aboutToClose); disconnect(d->dev, &QIODevice::bytesWritten, this, &QPacketProtocol::bytesWritten); d->dev = nullptr; - emit invalidPacket(); + emit error(); return; } - d->inProgressSize -= read; + d->inProgressSize -= sizeof(qint32); } else { - d->inProgress.append(d->dev->read(d->inProgressSize - d->inProgress.size())); + const int bytesToRead = static_cast<int>( + qMin(d->dev->bytesAvailable(), + static_cast<qint64>(d->inProgressSize - d->inProgress.size()))); + + QByteArray toRead(bytesToRead, Qt::Uninitialized); + if (!d->readFromDevice(toRead.data(), toRead.length())) { + emit error(); + return; + } + + d->inProgress.append(toRead); if (d->inProgressSize == d->inProgress.size()) { // Packet has arrived! d->packets.append(d->inProgress); @@ -281,6 +284,30 @@ QPacketProtocolPrivate::QPacketProtocolPrivate(QIODevice *dev) : { } +bool QPacketProtocolPrivate::writeToDevice(const char *bytes, qint64 size) +{ + qint64 totalWritten = 0; + while (totalWritten < size) { + const qint64 chunkSize = dev->write(bytes + totalWritten, size - totalWritten); + if (chunkSize < 0) + return false; + totalWritten += chunkSize; + } + return totalWritten == size; +} + +bool QPacketProtocolPrivate::readFromDevice(char *buffer, qint64 size) +{ + qint64 totalRead = 0; + while (totalRead < size) { + const qint64 chunkSize = dev->read(buffer + totalRead, size - totalRead); + if (chunkSize < 0) + return false; + totalRead += chunkSize; + } + return totalRead == size; +} + /*! \fn void QPacketProtocol::readyRead() diff --git a/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h b/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h index 35edb568aa..b401a58437 100644 --- a/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h +++ b/src/plugins/qmltooling/packetprotocol/qpacketprotocol_p.h @@ -72,10 +72,9 @@ public: Q_SIGNALS: void readyRead(); - void invalidPacket(); + void error(); private: - void aboutToClose(); void bytesWritten(qint64 bytes); void readyToRead(); }; diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp index 95e6d5704c..8c92b4b170 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.cpp @@ -46,6 +46,7 @@ #include <private/qv4objectiterator_p.h> #include <private/qv4identifier_p.h> #include <private/qv4runtime_p.h> +#include <private/qv4identifiertable_p.h> #include <private/qqmlcontext_p.h> #include <private/qqmlengine_p.h> @@ -96,33 +97,28 @@ int QV4DataCollector::encodeScopeType(QV4::Heap::ExecutionContext::ContextType s { switch (scopeType) { case QV4::Heap::ExecutionContext::Type_GlobalContext: - return 0; - case QV4::Heap::ExecutionContext::Type_CatchContext: - return 4; + break; case QV4::Heap::ExecutionContext::Type_WithContext: return 2; case QV4::Heap::ExecutionContext::Type_CallContext: return 1; case QV4::Heap::ExecutionContext::Type_QmlContext: return 3; - default: - return -1; + case QV4::Heap::ExecutionContext::Type_BlockContext: + return 4; } + return 0; } QV4DataCollector::QV4DataCollector(QV4::ExecutionEngine *engine) - : m_engine(engine), m_namesAsObjects(true), m_redundantRefs(true) + : m_engine(engine) { m_values.set(engine, engine->newArrayObject()); } -// TODO: Directly call addRef() once we don't need to support redundantRefs anymore -QV4DataCollector::Ref QV4DataCollector::collect(const QV4::ScopedValue &value) +QV4DataCollector::Ref QV4DataCollector::addValueRef(const QV4::ScopedValue &value) { - Ref ref = addRef(value); - if (m_redundantRefs) - m_collectedRefs.append(ref); - return ref; + return addRef(value); } const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::ExecutionEngine *engine, @@ -158,15 +154,12 @@ const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::Execution int numProperties = 0; QV4::ObjectIterator it(scope, o, QV4::ObjectIterator::EnumerableOnly); QV4::PropertyAttributes attrs; - uint index; - QV4::ScopedProperty p(scope); - QV4::ScopedString name(scope); + QV4::ScopedPropertyKey name(scope); while (true) { - it.next(name.getRef(), &index, p, &attrs); - if (attrs.isEmpty()) + name = it.next(nullptr, &attrs); + if (!name->isValid()) break; - else - ++numProperties; + ++numProperties; } dict.insert(valueKey, numProperties); return o; @@ -192,61 +185,21 @@ const QV4::Object *collectProperty(const QV4::ScopedValue &value, QV4::Execution } } -QJsonObject QV4DataCollector::lookupRef(Ref ref, bool deep) +QJsonObject QV4DataCollector::lookupRef(Ref ref) { QJsonObject dict; - if (m_namesAsObjects) { - if (lookupSpecialRef(ref, &dict)) - return dict; - } - - if (m_redundantRefs) - deep = true; - dict.insert(QStringLiteral("handle"), qint64(ref)); QV4::Scope scope(engine()); QV4::ScopedValue value(scope, getValue(ref)); const QV4::Object *object = collectProperty(value, engine(), dict); - if (deep && object) + if (object) dict.insert(QStringLiteral("properties"), collectProperties(object)); return dict; } -// TODO: Drop this method once we don't need to support namesAsObjects anymore -QV4DataCollector::Ref QV4DataCollector::addFunctionRef(const QString &functionName) -{ - Q_ASSERT(m_namesAsObjects); - Ref ref = addRef(QV4::Primitive::emptyValue(), false); - - QJsonObject dict; - dict.insert(QStringLiteral("handle"), qint64(ref)); - dict.insert(QStringLiteral("type"), QStringLiteral("function")); - dict.insert(QStringLiteral("name"), functionName); - m_specialRefs.insert(ref, dict); - m_collectedRefs.append(ref); - - return ref; -} - -// TODO: Drop this method once we don't need to support namesAsObjects anymore -QV4DataCollector::Ref QV4DataCollector::addScriptRef(const QString &scriptName) -{ - Q_ASSERT(m_namesAsObjects); - Ref ref = addRef(QV4::Primitive::emptyValue(), false); - - QJsonObject dict; - dict.insert(QStringLiteral("handle"), qint64(ref)); - dict.insert(QStringLiteral("type"), QStringLiteral("script")); - dict.insert(QStringLiteral("name"), scriptName); - m_specialRefs.insert(ref, dict); - m_collectedRefs.append(ref); - - return ref; -} - bool QV4DataCollector::isValidRef(QV4DataCollector::Ref ref) const { QV4::Scope scope(engine()); @@ -268,12 +221,12 @@ bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr) Refs collectedRefs; QV4::ScopedValue v(scope); - QV4::InternalClass *ic = ctxt->internalClass(); + QV4::Heap::InternalClass *ic = ctxt->internalClass(); for (uint i = 0; i < ic->size; ++i) { - QString name = ic->nameMap[i]->string; + QString name = ic->keyAt(i); names.append(name); v = static_cast<QV4::Heap::CallContext *>(ctxt->d())->locals[i]; - collectedRefs.append(collect(v)); + collectedRefs.append(addValueRef(v)); } Q_ASSERT(names.size() == collectedRefs.size()); @@ -284,13 +237,7 @@ bool QV4DataCollector::collectScope(QJsonObject *dict, int frameNr, int scopeNr) } } - Ref scopeObjectRef = addRef(scopeObject); - if (m_redundantRefs) { - dict->insert(QStringLiteral("ref"), qint64(scopeObjectRef)); - m_collectedRefs.append(scopeObjectRef); - } else { - *dict = lookupRef(scopeObjectRef, true); - } + *dict = lookupRef(addRef(scopeObject)); return true; } @@ -306,13 +253,8 @@ QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int QJsonObject frame; frame[QLatin1String("index")] = frameNr; frame[QLatin1String("debuggerFrame")] = false; - if (m_namesAsObjects) { - frame[QLatin1String("func")] = toRef(addFunctionRef(stackFrame.function)); - frame[QLatin1String("script")] = toRef(addScriptRef(stackFrame.source)); - } else { - frame[QLatin1String("func")] = stackFrame.function; - frame[QLatin1String("script")] = stackFrame.source; - } + frame[QLatin1String("func")] = stackFrame.function; + frame[QLatin1String("script")] = stackFrame.source; frame[QLatin1String("line")] = stackFrame.line - 1; if (stackFrame.column >= 0) frame[QLatin1String("column")] = stackFrame.column; @@ -330,7 +272,7 @@ QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int if (ctxt) { QV4::ScopedValue o(scope, ctxt->d()->activation); - frame[QLatin1String("receiver")] = toRef(collect(o)); + frame[QLatin1String("receiver")] = toRef(addValueRef(o)); } // Only type and index are used by Qt Creator, so we keep it easy: @@ -351,30 +293,9 @@ QJsonObject QV4DataCollector::buildFrame(const QV4::StackFrame &stackFrame, int return frame; } -// TODO: Drop this method once we don't need to support redundantRefs anymore -QJsonArray QV4DataCollector::flushCollectedRefs() -{ - Q_ASSERT(m_redundantRefs); - QJsonArray refs; - std::sort(m_collectedRefs.begin(), m_collectedRefs.end()); - for (int i = 0, ei = m_collectedRefs.size(); i != ei; ++i) { - QV4DataCollector::Ref ref = m_collectedRefs.at(i); - if (i > 0 && ref == m_collectedRefs.at(i - 1)) - continue; - refs.append(lookupRef(ref, true)); - } - - m_collectedRefs.clear(); - return refs; -} - void QV4DataCollector::clear() { m_values.set(engine(), engine()->newArrayObject()); - m_collectedRefs.clear(); - m_specialRefs.clear(); - m_namesAsObjects = true; - m_redundantRefs = true; } QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicate) @@ -394,18 +315,18 @@ QV4DataCollector::Ref QV4DataCollector::addRef(QV4::Value value, bool deduplicat { std::swap(*hasExceptionLoc, hadException); } }; - // if we wouldn't do this, the putIndexed won't work. + // if we wouldn't do this, the put won't work. ExceptionStateSaver resetExceptionState(engine()); QV4::Scope scope(engine()); QV4::ScopedObject array(scope, m_values.value()); if (deduplicate) { for (Ref i = 0; i < array->getLength(); ++i) { - if (array->getIndexed(i) == value.rawValue() && !m_specialRefs.contains(i)) + if (array->get(i) == value.rawValue()) return i; } } Ref ref = array->getLength(); - array->putIndexed(ref, value); + array->put(ref, value); Q_ASSERT(array->getLength() - 1 == ref); return ref; } @@ -415,23 +336,39 @@ QV4::ReturnedValue QV4DataCollector::getValue(Ref ref) QV4::Scope scope(engine()); QV4::ScopedObject array(scope, m_values.value()); Q_ASSERT(ref < array->getLength()); - return array->getIndexed(ref, nullptr); + return array->get(ref, nullptr); } -// TODO: Drop this method once we don't need to support namesAsObjects anymore -bool QV4DataCollector::lookupSpecialRef(Ref ref, QJsonObject *dict) +class CapturePreventer { - Q_ASSERT(m_namesAsObjects); - SpecialRefs::const_iterator it = m_specialRefs.constFind(ref); - if (it == m_specialRefs.cend()) - return false; +public: + CapturePreventer(QV4::ExecutionEngine *engine) + { + if (QQmlEngine *e = engine->qmlEngine()) { + m_engine = QQmlEnginePrivate::get(e); + m_capture = m_engine->propertyCapture; + m_engine->propertyCapture = nullptr; + } + } - *dict = it.value(); - return true; -} + ~CapturePreventer() + { + if (m_engine && m_capture) { + Q_ASSERT(!m_engine->propertyCapture); + m_engine->propertyCapture = m_capture; + } + } + +private: + QQmlEnginePrivate *m_engine = nullptr; + QQmlPropertyCapture *m_capture = nullptr; +}; QJsonArray QV4DataCollector::collectProperties(const QV4::Object *object) { + CapturePreventer capturePreventer(engine()); + Q_UNUSED(capturePreventer); + QJsonArray res; QV4::Scope scope(engine()); @@ -459,8 +396,6 @@ QJsonObject QV4DataCollector::collectAsJson(const QString &name, const QV4::Scop if (value->isManaged() && !value->isString()) { Ref ref = addRef(value); dict.insert(QStringLiteral("ref"), qint64(ref)); - if (m_redundantRefs) - m_collectedRefs.append(ref); } collectProperty(value, engine(), dict); diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h index 5494e10e9a..bc178fa2db 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4datacollector.h @@ -67,42 +67,27 @@ public: QV4DataCollector(QV4::ExecutionEngine *engine); - Ref collect(const QV4::ScopedValue &value); // only for redundantRefs - Ref addFunctionRef(const QString &functionName); // only for namesAsObjects - Ref addScriptRef(const QString &scriptName); // only for namesAsObjects - - void setNamesAsObjects(bool namesAsObjects) { m_namesAsObjects = namesAsObjects; } - bool namesAsObjects() const { return m_namesAsObjects; } - - void setRedundantRefs(bool redundantRefs) { m_redundantRefs = redundantRefs; } - bool redundantRefs() const { return m_redundantRefs; } + Ref addValueRef(const QV4::ScopedValue &value); bool isValidRef(Ref ref) const; - QJsonObject lookupRef(Ref ref, bool deep); + QJsonObject lookupRef(Ref ref); bool collectScope(QJsonObject *dict, int frameNr, int scopeNr); QJsonObject buildFrame(const QV4::StackFrame &stackFrame, int frameNr); QV4::ExecutionEngine *engine() const { return m_engine; } - QJsonArray flushCollectedRefs(); // only for redundantRefs void clear(); private: Ref addRef(QV4::Value value, bool deduplicate = true); QV4::ReturnedValue getValue(Ref ref); - bool lookupSpecialRef(Ref ref, QJsonObject *dict); // only for namesAsObjects QJsonArray collectProperties(const QV4::Object *object); QJsonObject collectAsJson(const QString &name, const QV4::ScopedValue &value); void collectArgumentsInContext(); QV4::ExecutionEngine *m_engine; - Refs m_collectedRefs; // only for redundantRefs QV4::PersistentValue m_values; - typedef QHash<Ref, QJsonObject> SpecialRefs; // only for namesAsObjects - SpecialRefs m_specialRefs; // only for namesAsObjects - bool m_namesAsObjects; - bool m_redundantRefs; }; QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugger.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugger.cpp index a1ed211a55..5521e7628b 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugger.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugger.cpp @@ -70,7 +70,7 @@ QV4Debugger::QV4Debugger(QV4::ExecutionEngine *engine) , m_pauseRequested(false) , m_haveBreakPoints(false) , m_breakOnThrow(false) - , m_returnedValue(engine, QV4::Primitive::undefinedValue()) + , m_returnedValue(engine, QV4::Value::undefinedValue()) , m_gatherSources(nullptr) , m_runningJob(nullptr) , m_collector(engine) @@ -157,7 +157,7 @@ void QV4Debugger::clearPauseRequest() QV4Debugger::ExecutionState QV4Debugger::currentExecutionState() const { ExecutionState state; - state.fileName = getFunction()->sourceFile(); + state.fileName = QUrl(getFunction()->sourceFile()).fileName(); state.lineNumber = engine()->currentStackFrame->lineNumber(); return state; @@ -288,7 +288,7 @@ void QV4Debugger::pauseAndWait(PauseReason reason) bool QV4Debugger::reallyHitTheBreakPoint(const QString &filename, int linenr) { QHash<BreakPoint, QString>::iterator it = m_breakPoints.find( - BreakPoint(filename.mid(filename.lastIndexOf('/') + 1), linenr)); + BreakPoint(QUrl(filename).fileName(), linenr)); if (it == m_breakPoints.end()) return false; QString condition = it.value(); diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp index 7950d21612..b424ef9f6c 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.cpp @@ -65,7 +65,7 @@ void JavaScriptJob::run() QV4::Scope scope(engine); QV4::ScopedContext ctx(scope, engine->currentStackFrame ? engine->currentContext() - : engine->rootContext()); + : engine->scriptContext()); QObject scopeObject; QV4::CppStackFrame *frame = engine->currentStackFrame; @@ -103,7 +103,7 @@ void JavaScriptJob::run() } } - QV4::Script script(ctx, QV4::Compiler::EvalCode, this->script); + QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, this->script); if (const QV4::Function *function = frame ? frame->v4Function : engine->globalCode) script.strictMode = function->isStrict(); @@ -150,7 +150,6 @@ void BacktraceJob::run() result.insert(QStringLiteral("toFrame"), fromFrame + frameArray.size()); result.insert(QStringLiteral("frames"), frameArray); } - flushRedundantRefs(); } FrameJob::FrameJob(QV4DataCollector *collector, int frameNr) : @@ -165,7 +164,6 @@ void FrameJob::run() success = false; } else { result = collector->buildFrame(frames[frameNr], frameNr); - flushRedundantRefs(); success = true; } } @@ -195,7 +193,6 @@ void ScopeJob::run() result[QLatin1String("index")] = scopeNr; result[QLatin1String("frameIndex")] = frameNr; result[QLatin1String("object")] = object; - flushRedundantRefs(); } bool ScopeJob::wasSuccessful() const @@ -228,9 +225,8 @@ void ValueLookupJob::run() exception = QString::fromLatin1("Invalid Ref: %1").arg(ref); break; } - result[QString::number(ref)] = collector->lookupRef(ref, true); + result[QString::number(ref)] = collector->lookupRef(ref); } - flushRedundantRefs(); } const QString &ValueLookupJob::exceptionMessage() const @@ -249,9 +245,7 @@ void ExpressionEvalJob::handleResult(QV4::ScopedValue &value) { if (hasExeption()) exception = value->toQStringNoThrow(); - result = collector->lookupRef(collector->collect(value), true); - if (collector->redundantRefs()) - collectedRefs = collector->flushCollectedRefs(); + result = collector->lookupRef(collector->addValueRef(value)); } const QString &ExpressionEvalJob::exceptionMessage() const @@ -264,13 +258,6 @@ const QJsonObject &ExpressionEvalJob::returnValue() const return result; } -// TODO: Drop this method once we don't need to support redundantRefs anymore -const QJsonArray &ExpressionEvalJob::refs() const -{ - Q_ASSERT(collector->redundantRefs()); - return collectedRefs; -} - GatherSourcesJob::GatherSourcesJob(QV4::ExecutionEngine *engine) : engine(engine) {} diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.h index eca8710e15..d1c7495863 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugjob.h @@ -78,24 +78,10 @@ class CollectJob : public QV4DebugJob protected: QV4DataCollector *collector; QJsonObject result; - QJsonArray collectedRefs; // only for redundantRefs - - void flushRedundantRefs() - { - if (collector->redundantRefs()) - collectedRefs = collector->flushCollectedRefs(); - } public: CollectJob(QV4DataCollector *collector) : collector(collector) {} const QJsonObject &returnValue() const { return result; } - - // TODO: Drop this method once we don't need to support redundantRefs anymore - const QJsonArray &refs() const - { - Q_ASSERT(collector->redundantRefs()); - return collectedRefs; - } }; class BacktraceJob: public CollectJob @@ -146,7 +132,6 @@ class ExpressionEvalJob: public JavaScriptJob QV4DataCollector *collector; QString exception; QJsonObject result; - QJsonArray collectedRefs; // only for redundantRefs public: ExpressionEvalJob(QV4::ExecutionEngine *engine, int frameNr, int context, @@ -154,7 +139,6 @@ public: void handleResult(QV4::ScopedValue &value) override; const QString &exceptionMessage() const; const QJsonObject &returnValue() const; - const QJsonArray &refs() const; // only for redundantRefs }; class GatherSourcesJob: public QV4DebugJob diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp index 32de8e9027..5866163ca6 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.cpp @@ -67,21 +67,21 @@ const char *const V4_PAUSE = "interrupt"; QT_BEGIN_NAMESPACE -class V8CommandHandler; -class UnknownV8CommandHandler; +class V4CommandHandler; +class UnknownV4CommandHandler; using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; int QV4DebugServiceImpl::sequence = 0; -class V8CommandHandler +class V4CommandHandler { public: - V8CommandHandler(const QString &command) + V4CommandHandler(const QString &command) : cmd(command) {} - virtual ~V8CommandHandler() + virtual ~V4CommandHandler() {} QString command() const { return cmd; } @@ -122,21 +122,6 @@ protected: response.insert(QStringLiteral("running"), debugService->debuggerAgent.isRunning()); } - QV4DataCollector *saneCollector(QV4Debugger *debugger) - { - QV4DataCollector *collector = debugger->collector(); - collector->setNamesAsObjects(debugService->clientRequiresNamesAsObjects()); - collector->setRedundantRefs(debugService->clientRequiresRedundantRefs()); - return collector; - } - - // TODO: drop this method once we don't need to support redundantRefs anymore. - void addRefs(const QJsonArray &refs) - { - Q_ASSERT(debugService->clientRequiresRedundantRefs()); - response.insert(QStringLiteral("refs"), refs); - } - void createErrorResponse(const QString &msg) { QJsonValue command = req.value(QLatin1String("command")); @@ -158,10 +143,10 @@ protected: QJsonObject response; }; -class UnknownV8CommandHandler: public V8CommandHandler +class UnknownV4CommandHandler: public V4CommandHandler { public: - UnknownV8CommandHandler(): V8CommandHandler(QString()) {} + UnknownV4CommandHandler(): V4CommandHandler(QString()) {} void handleRequest() override { @@ -173,10 +158,10 @@ public: }; namespace { -class V8VersionRequest: public V8CommandHandler +class V4VersionRequest: public V4CommandHandler { public: - V8VersionRequest(): V8CommandHandler(QStringLiteral("version")) {} + V4VersionRequest(): V4CommandHandler(QStringLiteral("version")) {} void handleRequest() override { @@ -189,98 +174,137 @@ public: QLatin1String("this is not V8, this is V4 in Qt " QT_VERSION_STR)); body.insert(QStringLiteral("UnpausedEvaluate"), true); body.insert(QStringLiteral("ContextEvaluate"), true); + body.insert(QStringLiteral("ChangeBreakpoint"), true); addBody(body); } }; -class V8SetBreakPointRequest: public V8CommandHandler +class V4BreakPointRequest: public V4CommandHandler { public: - V8SetBreakPointRequest(): V8CommandHandler(QStringLiteral("setbreakpoint")) {} + V4BreakPointRequest(const QString &name): V4CommandHandler(name) {} - void handleRequest() override + void handleRequest() final { + // Other types are currently not supported + m_type = QStringLiteral("scriptRegExp"); + // decypher the payload: - QJsonObject args = req.value(QLatin1String("arguments")).toObject(); - if (args.isEmpty()) + m_args = req.value(QLatin1String("arguments")).toObject(); + if (m_args.isEmpty()) { + createErrorResponse(QStringLiteral("breakpoint request with empty arguments object")); return; + } - QString type = args.value(QLatin1String("type")).toString(); + const int id = handleBreakPointRequest(); + if (id < 0) { + createErrorResponse(m_error); + } else { + // response: + addCommand(); + addRequestSequence(); + addSuccess(true); + addRunning(); + QJsonObject body; + body.insert(QStringLiteral("type"), m_type); + body.insert(QStringLiteral("breakpoint"), id); + addBody(body); + } + } + +protected: + virtual int handleBreakPointRequest() = 0; + + QJsonObject m_args; + QString m_type; + QString m_error; +}; + +class V4SetBreakPointRequest: public V4BreakPointRequest +{ +public: + V4SetBreakPointRequest(): V4BreakPointRequest(QStringLiteral("setbreakpoint")) {} + + int handleBreakPointRequest() final + { + // decypher the payload: + const QString type = m_args.value(QLatin1String("type")).toString(); if (type != QLatin1String("scriptRegExp")) { - createErrorResponse(QStringLiteral("breakpoint type \"%1\" is not implemented").arg(type)); - return; + m_error = QStringLiteral("breakpoint type \"%1\" is not implemented").arg(type); + return -1; } - QString fileName = args.value(QLatin1String("target")).toString(); + const QString fileName = m_args.value(QLatin1String("target")).toString(); if (fileName.isEmpty()) { - createErrorResponse(QStringLiteral("breakpoint has no file name")); - return; + m_error = QStringLiteral("breakpoint has no file name"); + return -1; } - int line = args.value(QLatin1String("line")).toInt(-1); + + const int line = m_args.value(QLatin1String("line")).toInt(-1); if (line < 0) { - createErrorResponse(QStringLiteral("breakpoint has an invalid line number")); - return; + m_error = QStringLiteral("breakpoint has an invalid line number"); + return -1; } - bool enabled = args.value(QStringLiteral("enabled")).toBool(true); - QString condition = args.value(QStringLiteral("condition")).toString(); + const bool enabled = m_args.value(QStringLiteral("enabled")).toBool(true); + const QString condition = m_args.value(QStringLiteral("condition")).toString(); // set the break point: - int id = debugService->debuggerAgent.addBreakPoint(fileName, line + 1, enabled, condition); + return debugService->debuggerAgent.addBreakPoint(fileName, line + 1, enabled, condition); - // response: - addCommand(); - addRequestSequence(); - addSuccess(true); - addRunning(); - QJsonObject body; - body.insert(QStringLiteral("type"), type); - body.insert(QStringLiteral("breakpoint"), id); // It's undocumented, but V8 sends back an actual_locations array too. However, our // Debugger currently doesn't tell us when it resolved a breakpoint, so we'll leave them // pending until the breakpoint is hit for the first time. - addBody(body); } }; -class V8ClearBreakPointRequest: public V8CommandHandler +class V4ClearBreakPointRequest: public V4BreakPointRequest { public: - V8ClearBreakPointRequest(): V8CommandHandler(QStringLiteral("clearbreakpoint")) {} + V4ClearBreakPointRequest(): V4BreakPointRequest(QStringLiteral("clearbreakpoint")) {} - void handleRequest() override + int handleBreakPointRequest() final { - // decypher the payload: - QJsonObject args = req.value(QLatin1String("arguments")).toObject(); - if (args.isEmpty()) - return; + const int id = m_args.value(QLatin1String("breakpoint")).toInt(-1); + if (id < 0) + m_error = QStringLiteral("breakpoint has an invalid number"); + else // remove the break point: + debugService->debuggerAgent.removeBreakPoint(id); - int id = args.value(QLatin1String("breakpoint")).toInt(-1); + return id; + } +}; + +class V4ChangeBreakPointRequest: public V4BreakPointRequest +{ +public: + V4ChangeBreakPointRequest(): V4BreakPointRequest(QStringLiteral("changebreakpoint")) {} + + int handleBreakPointRequest() final + { + const int id = m_args.value(QLatin1String("breakpoint")).toInt(-1); if (id < 0) { - createErrorResponse(QStringLiteral("breakpoint has an invalid number")); - return; + m_error = QStringLiteral("breakpoint has an invalid number"); + return id; } - // remove the break point: - debugService->debuggerAgent.removeBreakPoint(id); + const QJsonValue enabled = m_args.value(QLatin1String("enabled")); + if (!enabled.isBool()) { + m_error = QStringLiteral("missing bool \"enabled\" in breakpoint change request"); + return -1; + } - // response: - addCommand(); - addRequestSequence(); - addSuccess(true); - addRunning(); - QJsonObject body; - body.insert(QStringLiteral("type"), QStringLiteral("scriptRegExp")); - body.insert(QStringLiteral("breakpoint"), id); - addBody(body); + // enable or disable the break point: + debugService->debuggerAgent.enableBreakPoint(id, enabled.toBool()); + return id; } }; -class V8BacktraceRequest: public V8CommandHandler +class V4BacktraceRequest: public V4CommandHandler { public: - V8BacktraceRequest(): V8CommandHandler(QStringLiteral("backtrace")) {} + V4BacktraceRequest(): V4CommandHandler(QStringLiteral("backtrace")) {} void handleRequest() override { @@ -297,7 +321,7 @@ public: return; } - BacktraceJob job(saneCollector(debugger), fromFrame, toFrame); + BacktraceJob job(debugger->collector(), fromFrame, toFrame); debugger->runInEngine(&job); // response: @@ -306,15 +330,13 @@ public: addSuccess(true); addRunning(); addBody(job.returnValue()); - if (debugService->clientRequiresRedundantRefs()) - addRefs(job.refs()); } }; -class V8FrameRequest: public V8CommandHandler +class V4FrameRequest: public V4CommandHandler { public: - V8FrameRequest(): V8CommandHandler(QStringLiteral("frame")) {} + V4FrameRequest(): V4CommandHandler(QStringLiteral("frame")) {} void handleRequest() override { @@ -334,7 +356,7 @@ public: return; } - FrameJob job(saneCollector(debugger), frameNr); + FrameJob job(debugger->collector(), frameNr); debugger->runInEngine(&job); if (!job.wasSuccessful()) { createErrorResponse(QStringLiteral("frame retrieval failed")); @@ -349,15 +371,13 @@ public: addSuccess(true); addRunning(); addBody(job.returnValue()); - if (debugService->clientRequiresRedundantRefs()) - addRefs(job.refs()); } }; -class V8ScopeRequest: public V8CommandHandler +class V4ScopeRequest: public V4CommandHandler { public: - V8ScopeRequest(): V8CommandHandler(QStringLiteral("scope")) {} + V4ScopeRequest(): V4CommandHandler(QStringLiteral("scope")) {} void handleRequest() override { @@ -382,7 +402,7 @@ public: return; } - ScopeJob job(saneCollector(debugger), frameNr, scopeNr); + ScopeJob job(debugger->collector(), frameNr, scopeNr); debugger->runInEngine(&job); if (!job.wasSuccessful()) { createErrorResponse(QStringLiteral("scope retrieval failed")); @@ -395,15 +415,13 @@ public: addSuccess(true); addRunning(); addBody(job.returnValue()); - if (debugService->clientRequiresRedundantRefs()) - addRefs(job.refs()); } }; -class V8LookupRequest: public V8CommandHandler +class V4LookupRequest: public V4CommandHandler { public: - V8LookupRequest(): V8CommandHandler(QStringLiteral("lookup")) {} + V4LookupRequest(): V4CommandHandler(QStringLiteral("lookup")) {} void handleRequest() override { @@ -424,7 +442,7 @@ public: debugger = debuggers.first(); } - ValueLookupJob job(handles, saneCollector(debugger)); + ValueLookupJob job(handles, debugger->collector()); debugger->runInEngine(&job); if (!job.exceptionMessage().isEmpty()) { createErrorResponse(job.exceptionMessage()); @@ -435,16 +453,14 @@ public: addSuccess(true); addRunning(); addBody(job.returnValue()); - if (debugService->clientRequiresRedundantRefs()) - addRefs(job.refs()); } } }; -class V8ContinueRequest: public V8CommandHandler +class V4ContinueRequest: public V4CommandHandler { public: - V8ContinueRequest(): V8CommandHandler(QStringLiteral("continue")) {} + V4ContinueRequest(): V4CommandHandler(QStringLiteral("continue")) {} void handleRequest() override { @@ -487,10 +503,10 @@ public: } }; -class V8DisconnectRequest: public V8CommandHandler +class V4DisconnectRequest: public V4CommandHandler { public: - V8DisconnectRequest(): V8CommandHandler(QStringLiteral("disconnect")) {} + V4DisconnectRequest(): V4CommandHandler(QStringLiteral("disconnect")) {} void handleRequest() override { @@ -505,10 +521,10 @@ public: } }; -class V8SetExceptionBreakRequest: public V8CommandHandler +class V4SetExceptionBreakRequest: public V4CommandHandler { public: - V8SetExceptionBreakRequest(): V8CommandHandler(QStringLiteral("setexceptionbreak")) {} + V4SetExceptionBreakRequest(): V4CommandHandler(QStringLiteral("setexceptionbreak")) {} void handleRequest() override { @@ -545,10 +561,10 @@ public: } }; -class V8ScriptsRequest: public V8CommandHandler +class V4ScriptsRequest: public V4CommandHandler { public: - V8ScriptsRequest(): V8CommandHandler(QStringLiteral("scripts")) {} + V4ScriptsRequest(): V4CommandHandler(QStringLiteral("scripts")) {} void handleRequest() override { @@ -617,10 +633,10 @@ public: // } // // The "value" key in "body" is the result of evaluating the expression in the request. -class V8EvaluateRequest: public V8CommandHandler +class V4EvaluateRequest: public V4CommandHandler { public: - V8EvaluateRequest(): V8CommandHandler(QStringLiteral("evaluate")) {} + V4EvaluateRequest(): V4CommandHandler(QStringLiteral("evaluate")) {} void handleRequest() override { @@ -645,7 +661,7 @@ public: } ExpressionEvalJob job(debugger->engine(), frame, context, expression, - saneCollector(debugger)); + debugger->collector()); debugger->runInEngine(&job); if (job.hasExeption()) { createErrorResponse(job.exceptionMessage()); @@ -655,44 +671,43 @@ public: addSuccess(true); addRunning(); addBody(job.returnValue()); - if (debugService->clientRequiresRedundantRefs()) - addRefs(job.refs()); } } }; } // anonymous namespace -void QV4DebugServiceImpl::addHandler(V8CommandHandler* handler) +void QV4DebugServiceImpl::addHandler(V4CommandHandler* handler) { handlers[handler->command()] = handler; } -V8CommandHandler *QV4DebugServiceImpl::v8CommandHandler(const QString &command) const +V4CommandHandler *QV4DebugServiceImpl::v4CommandHandler(const QString &command) const { - V8CommandHandler *handler = handlers.value(command, 0); + V4CommandHandler *handler = handlers.value(command, 0); if (handler) return handler; else - return unknownV8CommandHandler.data(); + return unknownV4CommandHandler.data(); } QV4DebugServiceImpl::QV4DebugServiceImpl(QObject *parent) : QQmlConfigurableDebugService<QV4DebugService>(1, parent), - debuggerAgent(this), theSelectedFrame(0), redundantRefs(true), namesAsObjects(true), - unknownV8CommandHandler(new UnknownV8CommandHandler) + debuggerAgent(this), theSelectedFrame(0), + unknownV4CommandHandler(new UnknownV4CommandHandler) { - addHandler(new V8VersionRequest); - addHandler(new V8SetBreakPointRequest); - addHandler(new V8ClearBreakPointRequest); - addHandler(new V8BacktraceRequest); - addHandler(new V8FrameRequest); - addHandler(new V8ScopeRequest); - addHandler(new V8LookupRequest); - addHandler(new V8ContinueRequest); - addHandler(new V8DisconnectRequest); - addHandler(new V8SetExceptionBreakRequest); - addHandler(new V8ScriptsRequest); - addHandler(new V8EvaluateRequest); + addHandler(new V4VersionRequest); + addHandler(new V4SetBreakPointRequest); + addHandler(new V4ClearBreakPointRequest); + addHandler(new V4ChangeBreakPointRequest); + addHandler(new V4BacktraceRequest); + addHandler(new V4FrameRequest); + addHandler(new V4ScopeRequest); + addHandler(new V4LookupRequest); + addHandler(new V4ContinueRequest); + addHandler(new V4DisconnectRequest); + addHandler(new V4SetExceptionBreakRequest); + addHandler(new V4ScriptsRequest); + addHandler(new V4EvaluateRequest); } QV4DebugServiceImpl::~QV4DebugServiceImpl() @@ -782,12 +797,7 @@ void QV4DebugServiceImpl::messageReceived(const QByteArray &message) if (type == V4_CONNECT) { QJsonObject parameters = QJsonDocument::fromJson(payload).object(); - namesAsObjects = true; - redundantRefs = true; - if (parameters.contains("namesAsObjects")) - namesAsObjects = parameters.value("namesAsObjects").toBool(); - if (parameters.contains("redundantRefs")) - redundantRefs = parameters.value("redundantRefs").toBool(); + Q_UNUSED(parameters); // For future protocol changes emit messageToClient(name(), packMessage(type)); stopWaiting(); @@ -805,10 +815,10 @@ void QV4DebugServiceImpl::messageReceived(const QByteArray &message) else breakOnSignals.removeOne(signalName); } else if (type == "v8request") { - handleV8Request(payload); + handleV4Request(payload); } else if (type == V4_DISCONNECT) { TRACE_PROTOCOL(qDebug() << "... payload:" << payload.constData()); - handleV8Request(payload); + handleV4Request(payload); } else { sendSomethingToSomebody(type, 0); } @@ -823,7 +833,7 @@ void QV4DebugServiceImpl::sendSomethingToSomebody(const char *type, int magicNum emit messageToClient(name(), packMessage(type, rs.data())); } -void QV4DebugServiceImpl::handleV8Request(const QByteArray &payload) +void QV4DebugServiceImpl::handleV4Request(const QByteArray &payload) { TRACE_PROTOCOL(qDebug() << "v8request, payload:" << payload.constData()); @@ -832,7 +842,7 @@ void QV4DebugServiceImpl::handleV8Request(const QByteArray &payload) QJsonValue type = o.value(QLatin1String("type")); if (type.toString() == QLatin1String("request")) { QJsonValue command = o.value(QLatin1String("command")); - V8CommandHandler *h = v8CommandHandler(command.toString()); + V4CommandHandler *h = v4CommandHandler(command.toString()); if (h) h->handle(o, this); } @@ -846,11 +856,11 @@ QByteArray QV4DebugServiceImpl::packMessage(const QByteArray &command, const QBy return rs.data(); } -void QV4DebugServiceImpl::send(QJsonObject v8Payload) +void QV4DebugServiceImpl::send(QJsonObject v4Payload) { - v8Payload[QLatin1String("seq")] = sequence++; + v4Payload[QLatin1String("seq")] = sequence++; QJsonDocument doc; - doc.setObject(v8Payload); + doc.setObject(v4Payload); #ifdef NO_PROTOCOL_TRACING QByteArray responseData = doc.toJson(QJsonDocument::Compact); #else diff --git a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h index 5401956994..d0b104dfad 100644 --- a/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h +++ b/src/plugins/qmltooling/qmldbg_debugger/qv4debugservice.h @@ -64,15 +64,15 @@ QT_BEGIN_NAMESPACE namespace QV4 { struct ExecutionEngine; } class VariableCollector; -class V8CommandHandler; -class UnknownV8CommandHandler; +class V4CommandHandler; +class UnknownV4CommandHandler; class QV4DebugServiceImpl; class QV4DebugServiceImpl : public QQmlConfigurableDebugService<QV4DebugService> { Q_OBJECT public: - explicit QV4DebugServiceImpl(QObject *parent = 0); + explicit QV4DebugServiceImpl(QObject *parent = nullptr); ~QV4DebugServiceImpl() override; void engineAdded(QJSEngine *engine) override; @@ -81,14 +81,11 @@ public: void stateAboutToBeChanged(State state) override; void signalEmitted(const QString &signal) override; - void send(QJsonObject v8Payload); + void send(QJsonObject v4Payload); int selectedFrame() const; void selectFrame(int frameNr); - bool clientRequiresRedundantRefs() const { return redundantRefs; } - bool clientRequiresNamesAsObjects() const { return namesAsObjects; } - QV4DebuggerAgent debuggerAgent; protected: @@ -98,22 +95,19 @@ protected: private: friend class QQmlDebuggerServiceFactory; - void handleV8Request(const QByteArray &payload); + void handleV4Request(const QByteArray &payload); static QByteArray packMessage(const QByteArray &command, const QByteArray &message = QByteArray()); void processCommand(const QByteArray &command, const QByteArray &data); - V8CommandHandler *v8CommandHandler(const QString &command) const; + V4CommandHandler *v4CommandHandler(const QString &command) const; QStringList breakOnSignals; static int sequence; int theSelectedFrame; - bool redundantRefs; - bool namesAsObjects; - - void addHandler(V8CommandHandler* handler); - QHash<QString, V8CommandHandler*> handlers; - QScopedPointer<UnknownV8CommandHandler> unknownV8CommandHandler; + void addHandler(V4CommandHandler* handler); + QHash<QString, V4CommandHandler*> handlers; + QScopedPointer<UnknownV4CommandHandler> unknownV4CommandHandler; }; QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_inspector/highlight.cpp b/src/plugins/qmltooling/qmldbg_inspector/highlight.cpp index 0d6cd45354..c4d7872162 100644 --- a/src/plugins/qmltooling/qmldbg_inspector/highlight.cpp +++ b/src/plugins/qmltooling/qmldbg_inspector/highlight.cpp @@ -188,7 +188,7 @@ void SelectionHighlight::showName(const QPointF &displayPoint) { m_displayPoint = displayPoint; m_nameDisplayActive = true; - QTimer::singleShot(1500, this, SLOT(disableNameDisplay())); + QTimer::singleShot(1500, this, &SelectionHighlight::disableNameDisplay); update(); } diff --git a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp index b19115aa60..a5c2f40420 100644 --- a/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp +++ b/src/plugins/qmltooling/qmldbg_nativedebugger/qqmlnativedebugservice.cpp @@ -51,6 +51,7 @@ #include <private/qv4runtime_p.h> #include <private/qversionedpacket_p.h> #include <private/qqmldebugserviceinterfaces_p.h> +#include <private/qv4identifiertable_p.h> #include <QtQml/qjsengine.h> #include <QtCore/qjsonarray.h> @@ -252,9 +253,9 @@ QV4::ReturnedValue NativeDebugger::evaluateExpression(const QString &expression) m_runningJob = true; QV4::ExecutionContext *ctx = m_engine->currentStackFrame ? m_engine->currentContext() - : m_engine->rootContext(); + : m_engine->scriptContext(); - QV4::Script script(ctx, QV4::Compiler::EvalCode, expression); + QV4::Script script(ctx, QV4::Compiler::ContextType::Eval, expression); if (const QV4::Function *function = m_engine->currentStackFrame ? m_engine->currentStackFrame->v4Function : m_engine->globalCode) script.strictMode = function->isStrict(); @@ -276,7 +277,7 @@ QV4::ReturnedValue NativeDebugger::evaluateExpression(const QString &expression) } NativeDebugger::NativeDebugger(QQmlNativeDebugServiceImpl *service, QV4::ExecutionEngine *engine) - : m_returnedValue(engine, QV4::Primitive::undefinedValue()) + : m_returnedValue(engine, QV4::Value::undefinedValue()) { m_stepping = NotStepping; m_pauseRequested = false; @@ -414,7 +415,7 @@ void Collector::collect(QJsonArray *out, const QString &parentIName, const QStri if (isExpanded(iname)) { QJsonArray children; for (uint i = 0; i < n; ++i) { - QV4::ReturnedValue v = array->getIndexed(i); + QV4::ReturnedValue v = array->get(i); QV4::ScopedValue sval(scope, v); collect(&children, iname, QString::number(i), *sval); } @@ -426,20 +427,17 @@ void Collector::collect(QJsonArray *out, const QString &parentIName, const QStri qint64 numProperties = 0; QV4::ObjectIterator it(scope, object, QV4::ObjectIterator::EnumerableOnly); QV4::ScopedProperty p(scope); - QV4::ScopedString name(scope); + QV4::ScopedPropertyKey name(scope); while (true) { QV4::PropertyAttributes attrs; - uint index; - it.next(name.getRef(), &index, p, &attrs); - if (attrs.isEmpty()) + name = it.next(p, &attrs); + if (!name->isValid()) break; - if (name.getPointer()) { + if (name->isStringOrSymbol()) { ++numProperties; if (expanded) { - if (name.getPointer()) { - QV4::Value v = p.property->value; - collect(&children, iname, name->toQStringNoThrow(), v); - } + QV4::Value v = p.property->value; + collect(&children, iname, name->toQString(), v); } } } @@ -493,10 +491,10 @@ void NativeDebugger::handleVariables(QJsonObject *response, const QJsonObject &a collector.collect(&output, QString(), QStringLiteral("this"), thisObject); QV4::Scoped<QV4::CallContext> callContext(scope, frame->callContext()); if (callContext) { - QV4::InternalClass *ic = callContext->internalClass(); + QV4::Heap::InternalClass *ic = callContext->internalClass(); QV4::ScopedValue v(scope); for (uint i = 0; i < ic->size; ++i) { - QString name = ic->nameMap[i]->string; + QString name = ic->keyAt(i); v = callContext->d()->locals[i]; collector.collect(&output, QString(), name, v); } @@ -691,8 +689,7 @@ bool NativeDebugger::reallyHitTheBreakPoint(const QV4::Function *function, int l for (int i = 0, n = m_service->m_breakHandler->m_breakPoints.size(); i != n; ++i) { const BreakPoint &bp = m_service->m_breakHandler->m_breakPoints.at(i); if (bp.lineNumber == lineNumber) { - const QString fileName = function->sourceFile(); - const QStringRef base = fileName.midRef(fileName.lastIndexOf('/') + 1); + const QString base = QUrl(function->sourceFile()).fileName(); if (bp.fileName.endsWith(base)) { if (bp.condition.isEmpty() || checkCondition(bp.condition)) { BreakPoint &mbp = m_service->m_breakHandler->m_breakPoints[i]; diff --git a/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro b/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro new file mode 100644 index 0000000000..08686a43e3 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qmldbg_preview.pro @@ -0,0 +1,29 @@ +QT += core-private qml-private packetprotocol-private network quick-private gui-private + +TARGET = qmldbg_preview + +SOURCES += \ + $$PWD/qqmlpreviewblacklist.cpp \ + $$PWD/qqmlpreviewfileengine.cpp \ + $$PWD/qqmlpreviewfileloader.cpp \ + $$PWD/qqmlpreviewhandler.cpp \ + $$PWD/qqmlpreviewposition.cpp \ + $$PWD/qqmlpreviewservice.cpp \ + $$PWD/qqmlpreviewservicefactory.cpp + +HEADERS += \ + $$PWD/qqmlpreviewblacklist.h \ + $$PWD/qqmlpreviewfileengine.h \ + $$PWD/qqmlpreviewfileloader.h \ + $$PWD/qqmlpreviewhandler.h \ + $$PWD/qqmlpreviewposition.h \ + $$PWD/qqmlpreviewservice.h \ + $$PWD/qqmlpreviewservicefactory.h + +OTHER_FILES += \ + $$PWD/qqmlpreviewservice.json + +PLUGIN_TYPE = qmltooling +PLUGIN_CLASS_NAME = QQmlPreviewServiceFactory + +load(qt_plugin) diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp new file mode 100644 index 0000000000..77fe69821c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.cpp @@ -0,0 +1,213 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewblacklist.h" + +QT_BEGIN_NAMESPACE + +void QQmlPreviewBlacklist::blacklist(const QString &path) +{ + if (!path.isEmpty()) + m_root.insert(path, 0); +} + +void QQmlPreviewBlacklist::whitelist(const QString &path) +{ + if (!path.isEmpty()) + m_root.remove(path, 0); +} + +bool QQmlPreviewBlacklist::isBlacklisted(const QString &path) const +{ + return path.isEmpty() ? true : m_root.containedPrefixLeaf(path, 0) > 0; +} + +void QQmlPreviewBlacklist::clear() +{ + m_root = Node(); +} + +QQmlPreviewBlacklist::Node::Node() +{ +} + +QQmlPreviewBlacklist::Node::Node(const QQmlPreviewBlacklist::Node &other) : + m_mine(other.m_mine), m_isLeaf(other.m_isLeaf) +{ + for (auto it = other.m_next.begin(), end = other.m_next.end(); it != end; ++it) + m_next.insert(it.key(), new Node(**it)); +} + +QQmlPreviewBlacklist::Node::Node(QQmlPreviewBlacklist::Node &&other) Q_DECL_NOEXCEPT +{ + m_mine.swap(other.m_mine); + m_next.swap(other.m_next); + m_isLeaf = other.m_isLeaf; +} + +QQmlPreviewBlacklist::Node::~Node() +{ + qDeleteAll(m_next); +} + +QQmlPreviewBlacklist::Node &QQmlPreviewBlacklist::Node::operator=( + const QQmlPreviewBlacklist::Node &other) +{ + if (&other != this) { + m_mine = other.m_mine; + for (auto it = other.m_next.begin(), end = other.m_next.end(); it != end; ++it) + m_next.insert(it.key(), new Node(**it)); + m_isLeaf = other.m_isLeaf; + } + return *this; +} + +QQmlPreviewBlacklist::Node &QQmlPreviewBlacklist::Node::operator=( + QQmlPreviewBlacklist::Node &&other) Q_DECL_NOEXCEPT +{ + if (&other != this) { + m_mine.swap(other.m_mine); + m_next.swap(other.m_next); + m_isLeaf = other.m_isLeaf; + } + return *this; +} + +void QQmlPreviewBlacklist::Node::split(QString::iterator it, QString::iterator end) +{ + QString existing; + existing.resize(end - it - 1); + std::copy(it + 1, end, existing.begin()); + + Node *node = new Node(existing, m_next, m_isLeaf); + m_next.clear(); + m_next.insert(*it, node); + m_mine.resize(it - m_mine.begin()); + m_isLeaf = false; +} + +void QQmlPreviewBlacklist::Node::insert(const QString &path, int offset) +{ + for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { + if (offset == path.size()) { + split(it, end); + m_isLeaf = true; + return; + } + + if (path.at(offset) != *it) { + split(it, end); + + QString inserted; + inserted.resize(path.size() - offset - 1); + std::copy(path.begin() + offset + 1, path.end(), inserted.begin()); + m_next.insert(path.at(offset), new Node(inserted)); + return; + } + + ++offset; + } + + if (offset == path.size()) { + m_isLeaf = true; + return; + } + + Node *&node = m_next[path.at(offset++)]; + if (node == nullptr) { + QString inserted; + inserted.resize(path.size() - offset); + std::copy(path.begin() + offset, path.end(), inserted.begin()); + node = new Node(inserted); + } else { + node->insert(path, offset); + } +} + +void QQmlPreviewBlacklist::Node::remove(const QString &path, int offset) +{ + for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { + if (offset == path.size() || path.at(offset) != *it) { + split(it, end); + return; + } + ++offset; + } + + m_isLeaf = false; + if (offset == path.size()) + return; + + auto it = m_next.find(path.at(offset)); + if (it != m_next.end()) + (*it)->remove(path, ++offset); +} + +int QQmlPreviewBlacklist::Node::containedPrefixLeaf(const QString &path, int offset) const +{ + if (offset == path.size()) + return (m_mine.isEmpty() && m_isLeaf) ? offset : -1; + + for (auto it = m_mine.begin(), end = m_mine.end(); it != end; ++it) { + if (path.at(offset) != *it) + return -1; + + if (++offset == path.size()) + return (++it == end && m_isLeaf) ? offset : -1; + } + + const QChar c = path.at(offset); + if (m_isLeaf && c == '/') + return offset; + + auto it = m_next.find(c); + if (it == m_next.end()) + return -1; + + return (*it)->containedPrefixLeaf(path, ++offset); +} + +QQmlPreviewBlacklist::Node::Node(const QString &mine, + const QHash<QChar, QQmlPreviewBlacklist::Node *> &next, + bool isLeaf) + : m_mine(mine), m_next(next), m_isLeaf(isLeaf) +{ +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h new file mode 100644 index 0000000000..2f743ca7a6 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewblacklist.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWBLACKLIST_H +#define QQMLPREVIEWBLACKLIST_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qhash.h> +#include <QtCore/qchar.h> +#include <QtCore/qstring.h> +#include <algorithm> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewBlacklist +{ +public: + void blacklist(const QString &path); + void whitelist(const QString &path); + bool isBlacklisted(const QString &path) const; + void clear(); + +private: + class Node { + public: + Node(); + Node(const Node &other); + Node(Node &&other) Q_DECL_NOEXCEPT; + + ~Node(); + + Node &operator=(const Node &other); + Node &operator=(Node &&other) Q_DECL_NOEXCEPT; + + void split(QString::iterator it, QString::iterator end); + void insert(const QString &path, int offset); + void remove(const QString &path, int offset); + int containedPrefixLeaf(const QString &path, int offset) const; + + private: + Node(const QString &mine, const QHash<QChar, Node *> &next = QHash<QChar, Node *>(), + bool isLeaf = true); + + QString m_mine; + QHash<QChar, Node *> m_next; + bool m_isLeaf = false; + }; + + Node m_root; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWBLACKLIST_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp new file mode 100644 index 0000000000..72de52bbe1 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp @@ -0,0 +1,443 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewfileengine.h" +#include "qqmlpreviewservice.h" + +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qthread.h> +#include <QtCore/qwaitcondition.h> + +#include <cstring> + +QT_BEGIN_NAMESPACE + +static bool isRelative(const QString &path) +{ + if (path.isEmpty()) + return true; + if (path.at(0) == '/') + return false; + if (path.at(0) == ':' && path.length() >= 2 && path.at(1) == '/') + return false; +#ifdef Q_OS_WIN + if (path.length() >= 2 && path.at(1) == ':') + return false; +#endif + return true; +} + +static QString absolutePath(const QString &path) +{ + return QDir::cleanPath(isRelative(path) ? (QDir::currentPath() + '/' + path) : path); +} + +bool isRootPath(const QString &path) +{ + return QFileSystemEntry::isRootPath(path); +} + +class QQmlPreviewFileEngineIterator : public QAbstractFileEngineIterator +{ +public: + QQmlPreviewFileEngineIterator(QDir::Filters filters, const QStringList &filterNames, + const QStringList &m_entries); + ~QQmlPreviewFileEngineIterator(); + + QString next() override; + bool hasNext() const override; + QString currentFileName() const override; + +private: + const QStringList m_entries; + int m_index; +}; + +QQmlPreviewFileEngineIterator::QQmlPreviewFileEngineIterator(QDir::Filters filters, + const QStringList &filterNames, + const QStringList &entries) + : QAbstractFileEngineIterator(filters, filterNames), m_entries(entries), m_index(0) +{ +} + +QQmlPreviewFileEngineIterator::~QQmlPreviewFileEngineIterator() +{ +} + +QString QQmlPreviewFileEngineIterator::next() +{ + if (!hasNext()) + return QString(); + ++m_index; + return currentFilePath(); +} + +bool QQmlPreviewFileEngineIterator::hasNext() const +{ + return m_index < m_entries.size(); +} + +QString QQmlPreviewFileEngineIterator::currentFileName() const +{ + if (m_index == 0 || m_index > m_entries.size()) + return QString(); + return m_entries.at(m_index - 1); +} + +QQmlPreviewFileEngine::QQmlPreviewFileEngine(const QString &file, const QString &absolute, + QQmlPreviewFileLoader *loader) : + m_name(file), m_absolute(absolute), m_loader(loader) +{ + load(); +} + +void QQmlPreviewFileEngine::setFileName(const QString &file) +{ + m_name = file; + m_absolute = absolutePath(file); + m_fallback.reset(); + m_contents.close(); + m_contents.setData(QByteArray()); + m_entries.clear(); + load(); +} + +bool QQmlPreviewFileEngine::open(QIODevice::OpenMode flags) +{ + switch (m_result) { + case QQmlPreviewFileLoader::File: + return m_contents.open(flags); + case QQmlPreviewFileLoader::Directory: + return false; + case QQmlPreviewFileLoader::Fallback: + return m_fallback->open(flags); + default: + Q_UNREACHABLE(); + return false; + } +} + +bool QQmlPreviewFileEngine::close() +{ + switch (m_result) { + case QQmlPreviewFileLoader::Fallback: + return m_fallback->close(); + case QQmlPreviewFileLoader::File: + m_contents.close(); + return true; + case QQmlPreviewFileLoader::Directory: + return false; + default: + Q_UNREACHABLE(); + return false; + } +} + +qint64 QQmlPreviewFileEngine::size() const +{ + return m_fallback ? m_fallback->size() : m_contents.size(); +} + +qint64 QQmlPreviewFileEngine::pos() const +{ + return m_fallback ? m_fallback->pos() : m_contents.pos(); +} + +bool QQmlPreviewFileEngine::seek(qint64 newPos) +{ + return m_fallback? m_fallback->seek(newPos) : m_contents.seek(newPos); +} + +qint64 QQmlPreviewFileEngine::read(char *data, qint64 maxlen) +{ + return m_fallback ? m_fallback->read(data, maxlen) : m_contents.read(data, maxlen); +} + +QAbstractFileEngine::FileFlags QQmlPreviewFileEngine::fileFlags( + QAbstractFileEngine::FileFlags type) const +{ + if (m_fallback) + return m_fallback->fileFlags(type); + + QAbstractFileEngine::FileFlags ret = 0; + + if (type & PermsMask) { + ret |= QAbstractFileEngine::FileFlags( + ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm); + } + + if (type & TypesMask) { + if (m_result == QQmlPreviewFileLoader::Directory) + ret |= DirectoryType; + else + ret |= FileType; + } + + if (type & FlagsMask) { + ret |= ExistsFlag; + if (isRootPath(m_name)) + ret |= RootFlag; + } + + return ret; +} + +QString QQmlPreviewFileEngine::fileName(QAbstractFileEngine::FileName file) const +{ + if (m_fallback) + return m_fallback->fileName(file); + + if (file == BaseName) { + int slashPos = m_name.lastIndexOf('/'); + if (slashPos == -1) + return m_name; + return m_name.mid(slashPos + 1); + } else if (file == PathName || file == AbsolutePathName) { + const QString path = (file == AbsolutePathName) ? m_absolute : m_name; + const int slashPos = path.lastIndexOf('/'); + if (slashPos == -1) + return QString(); + else if (slashPos == 0) + return "/"; + return path.left(slashPos); + } else if (file == CanonicalName || file == CanonicalPathName) { + if (file == CanonicalPathName) { + const int slashPos = m_absolute.lastIndexOf('/'); + if (slashPos != -1) + return m_absolute.left(slashPos); + } + return m_absolute; + } + return m_name; +} + +uint QQmlPreviewFileEngine::ownerId(QAbstractFileEngine::FileOwner owner) const +{ + return m_fallback ? m_fallback->ownerId(owner) : static_cast<uint>(-2); +} + +QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::beginEntryList(QDir::Filters filters, + const QStringList &filterNames) +{ + return m_fallback ? m_fallback->beginEntryList(filters, filterNames) + : new QQmlPreviewFileEngineIterator(filters, filterNames, m_entries); +} + +QAbstractFileEngine::Iterator *QQmlPreviewFileEngine::endEntryList() +{ + return m_fallback ? m_fallback->endEntryList() : nullptr; +} + +bool QQmlPreviewFileEngine::flush() +{ + return m_fallback ? m_fallback->flush() : true; +} + +bool QQmlPreviewFileEngine::syncToDisk() +{ + return m_fallback ? m_fallback->syncToDisk() : false; +} + +bool QQmlPreviewFileEngine::isSequential() const +{ + return m_fallback ? m_fallback->isSequential() : m_contents.isSequential(); +} + +bool QQmlPreviewFileEngine::remove() +{ + return m_fallback ? m_fallback->remove() : false; +} + +bool QQmlPreviewFileEngine::copy(const QString &newName) +{ + return m_fallback ? m_fallback->copy(newName) : false; +} + +bool QQmlPreviewFileEngine::rename(const QString &newName) +{ + return m_fallback ? m_fallback->rename(newName) : false; +} + +bool QQmlPreviewFileEngine::renameOverwrite(const QString &newName) +{ + return m_fallback ? m_fallback->renameOverwrite(newName) : false; +} + +bool QQmlPreviewFileEngine::link(const QString &newName) +{ + return m_fallback ? m_fallback->link(newName) : false; +} + +bool QQmlPreviewFileEngine::mkdir(const QString &dirName, bool createParentDirectories) const +{ + return m_fallback ? m_fallback->mkdir(dirName, createParentDirectories) : false; +} + +bool QQmlPreviewFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const +{ + return m_fallback ? m_fallback->rmdir(dirName, recurseParentDirectories) : false; +} + +bool QQmlPreviewFileEngine::setSize(qint64 size) +{ + switch (m_result) { + case QQmlPreviewFileLoader::Fallback: + return m_fallback->setSize(size); + case QQmlPreviewFileLoader::File: + if (size < 0 || size > std::numeric_limits<int>::max()) + return false; + m_contents.buffer().resize(static_cast<int>(size)); + return true; + case QQmlPreviewFileLoader::Directory: + return false; + default: + Q_UNREACHABLE(); + return false; + } +} + +bool QQmlPreviewFileEngine::caseSensitive() const +{ + return m_fallback ? m_fallback->caseSensitive() : true; +} + +bool QQmlPreviewFileEngine::isRelativePath() const +{ + return m_fallback ? m_fallback->isRelativePath() : isRelative(m_name); +} + +QStringList QQmlPreviewFileEngine::entryList(QDir::Filters filters, + const QStringList &filterNames) const +{ + return m_fallback ? m_fallback->entryList(filters, filterNames) + : QAbstractFileEngine::entryList(filters, filterNames); +} + +bool QQmlPreviewFileEngine::setPermissions(uint perms) +{ + return m_fallback ? m_fallback->setPermissions(perms) : false; +} + +QByteArray QQmlPreviewFileEngine::id() const +{ + return m_fallback ? m_fallback->id() : QByteArray(); +} + +QString QQmlPreviewFileEngine::owner(FileOwner owner) const +{ + return m_fallback ? m_fallback->owner(owner) : QString(); +} + +QDateTime QQmlPreviewFileEngine::fileTime(FileTime time) const +{ + // Files we replace are always newer than the ones we had before. This makes the QML engine + // actually recompile them, rather than pick them from the cache. + return m_fallback ? m_fallback->fileTime(time) : QDateTime::currentDateTime(); +} + +int QQmlPreviewFileEngine::handle() const +{ + return m_fallback ? m_fallback->handle() : -1; +} + +qint64 QQmlPreviewFileEngine::readLine(char *data, qint64 maxlen) +{ + return m_fallback ? m_fallback->readLine(data, maxlen) : m_contents.readLine(data, maxlen); +} + +qint64 QQmlPreviewFileEngine::write(const char *data, qint64 len) +{ + return m_fallback ? m_fallback->write(data, len) : m_contents.write(data, len); +} + +bool QQmlPreviewFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) +{ + return m_fallback ? m_fallback->extension(extension, option, output) : false; +} + +bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const +{ + return m_fallback ? m_fallback->supportsExtension(extension) : false; +} + +void QQmlPreviewFileEngine::load() const +{ + m_result = m_loader->load(m_absolute); + switch (m_result) { + case QQmlPreviewFileLoader::File: + m_contents.setData(m_loader->contents()); + break; + case QQmlPreviewFileLoader::Directory: + m_entries = m_loader->entries(); + break; + case QQmlPreviewFileLoader::Fallback: + m_fallback.reset(QAbstractFileEngine::create(m_name)); + break; + case QQmlPreviewFileLoader::Unknown: + Q_UNREACHABLE(); + break; + } +} + +QQmlPreviewFileEngineHandler::QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader) + : m_loader(loader) +{ +} + +QAbstractFileEngine *QQmlPreviewFileEngineHandler::create(const QString &fileName) const +{ + // Don't load compiled QML/JS over the network + if (fileName.endsWith(".qmlc") || fileName.endsWith(".jsc") || isRootPath(fileName)) { + return nullptr; + } + + QString relative = fileName; + while (relative.endsWith('/')) + relative.chop(1); + + if (relative.isEmpty() || relative == ":") + return nullptr; + + const QString absolute = relative.startsWith(':') ? relative : absolutePath(relative); + + return m_loader->isBlacklisted(absolute) + ? nullptr : new QQmlPreviewFileEngine(relative, absolute, m_loader.data()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h new file mode 100644 index 0000000000..9a40b6360c --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.h @@ -0,0 +1,136 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWFILEENGINE_H +#define QQMLPREVIEWFILEENGINE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewfileloader.h" + +#include <private/qabstractfileengine_p.h> +#include <private/qfsfileengine_p.h> +#include <QtCore/qbuffer.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewFileEngine : public QAbstractFileEngine +{ +public: + QQmlPreviewFileEngine(const QString &file, const QString &absolute, + QQmlPreviewFileLoader *loader); + + void setFileName(const QString &file) override; + + bool open(QIODevice::OpenMode flags) override ; + bool close() override; + qint64 size() const override; + qint64 pos() const override; + bool seek(qint64) override; + qint64 read(char *data, qint64 maxlen) override; + + FileFlags fileFlags(FileFlags type) const override; + QString fileName(QAbstractFileEngine::FileName file) const override; + uint ownerId(FileOwner) const override; + + Iterator *beginEntryList(QDir::Filters filters, const QStringList &filterNames) override; + Iterator *endEntryList() override; + + // Forwarding to fallback if exists + bool flush() override; + bool syncToDisk() override; + bool isSequential() const override; + bool remove() override; + bool copy(const QString &newName) override; + bool rename(const QString &newName) override; + bool renameOverwrite(const QString &newName) override; + bool link(const QString &newName) override; + bool mkdir(const QString &dirName, bool createParentDirectories) const override; + bool rmdir(const QString &dirName, bool recurseParentDirectories) const override; + bool setSize(qint64 size) override; + bool caseSensitive() const override; + bool isRelativePath() const override; + QStringList entryList(QDir::Filters filters, const QStringList &filterNames) const override; + bool setPermissions(uint perms) override; + QByteArray id() const override; + QString owner(FileOwner) const override; + QDateTime fileTime(FileTime time) const override; + int handle() const override; + qint64 readLine(char *data, qint64 maxlen) override; + qint64 write(const char *data, qint64 len) override; + bool extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output) override; + bool supportsExtension(Extension extension) const override; + +private: + void load() const; + + QString m_name; + QString m_absolute; + QPointer<QQmlPreviewFileLoader> m_loader; + + mutable QBuffer m_contents; + mutable QStringList m_entries; + mutable QScopedPointer<QAbstractFileEngine> m_fallback; + mutable QQmlPreviewFileLoader::Result m_result = QQmlPreviewFileLoader::Unknown; +}; + +class QQmlPreviewFileEngineHandler : public QAbstractFileEngineHandler +{ +public: + QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader); + QAbstractFileEngine *create(const QString &fileName) const override; + +private: + QPointer<QQmlPreviewFileLoader> m_loader; +}; + + + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWFILEENGINE_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp new file mode 100644 index 0000000000..9927089e5e --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.cpp @@ -0,0 +1,197 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewfileloader.h" +#include "qqmlpreviewservice.h" + +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qstandardpaths.h> + +QT_BEGIN_NAMESPACE + +QQmlPreviewFileLoader::QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service) : m_service(service) +{ + // Exclude some resource paths used by Qt itself. There is no point in loading those from the + // client as the client will not have the files (or even worse, it may have different ones). + m_blacklist.blacklist(":/qt-project.org"); + m_blacklist.blacklist(":/QtQuick/Controls/Styles"); + m_blacklist.blacklist(":/ExtrasImports/QtQuick/Controls/Styles"); + m_blacklist.blacklist(":/qgradient"); + + // Target specific configuration should not replaced with files from the host. + m_blacklist.blacklist("/etc"); + + for (int loc = QLibraryInfo::PrefixPath; loc < QLibraryInfo::TestsPath; ++loc) { + m_blacklist.blacklist(QLibraryInfo::location( + static_cast<QLibraryInfo::LibraryLocation>(loc))); + } + m_blacklist.blacklist(QLibraryInfo::location(QLibraryInfo::SettingsPath)); + + static const QStandardPaths::StandardLocation blackListLocations[] = { + QStandardPaths::DataLocation, + QStandardPaths::CacheLocation, + QStandardPaths::GenericDataLocation, + QStandardPaths::ConfigLocation, + QStandardPaths::GenericCacheLocation, + QStandardPaths::GenericConfigLocation, + QStandardPaths::AppDataLocation, + QStandardPaths::AppConfigLocation + }; + + for (auto locationType : blackListLocations) { + const QStringList locations = QStandardPaths::standardLocations(locationType); + for (const QString &location : locations) + m_blacklist.blacklist(location); + } + + m_blacklist.whitelist(QLibraryInfo::location(QLibraryInfo::TestsPath)); + + connect(this, &QQmlPreviewFileLoader::request, service, &QQmlPreviewServiceImpl::forwardRequest, + Qt::DirectConnection); + connect(service, &QQmlPreviewServiceImpl::directory, this, &QQmlPreviewFileLoader::directory); + connect(service, &QQmlPreviewServiceImpl::file, this, &QQmlPreviewFileLoader::file); + connect(service, &QQmlPreviewServiceImpl::error, this, &QQmlPreviewFileLoader::error); + connect(service, &QQmlPreviewServiceImpl::clearCache, this, &QQmlPreviewFileLoader::clearCache); + moveToThread(&m_thread); + m_thread.start(); +} + +QQmlPreviewFileLoader::~QQmlPreviewFileLoader() { + m_thread.quit(); + m_thread.wait(); +} + +QQmlPreviewFileLoader::Result QQmlPreviewFileLoader::load(const QString &path) +{ + QMutexLocker locker(&m_mutex); + m_path = path; + + auto fileIterator = m_fileCache.constFind(path); + if (fileIterator != m_fileCache.constEnd()) { + m_result = File; + m_contents = *fileIterator; + m_entries.clear(); + return m_result; + } + + auto dirIterator = m_directoryCache.constFind(path); + if (dirIterator != m_directoryCache.constEnd()) { + m_result = Directory; + m_contents.clear(); + m_entries = *dirIterator; + return m_result; + } + + m_result = Unknown; + m_entries.clear(); + m_contents.clear(); + emit request(path); + m_waitCondition.wait(&m_mutex); + return m_result; +} + +QByteArray QQmlPreviewFileLoader::contents() +{ + QMutexLocker locker(&m_mutex); + return m_contents; +} + +QStringList QQmlPreviewFileLoader::entries() +{ + QMutexLocker locker(&m_mutex); + return m_entries; +} + +void QQmlPreviewFileLoader::whitelist(const QUrl &url) +{ + const QString path = QQmlFile::urlToLocalFileOrQrc(url); + if (!path.isEmpty()) { + QMutexLocker locker(&m_mutex); + m_blacklist.whitelist(path); + } +} + +bool QQmlPreviewFileLoader::isBlacklisted(const QString &path) +{ + QMutexLocker locker(&m_mutex); + return m_blacklist.isBlacklisted(path); +} + +void QQmlPreviewFileLoader::file(const QString &path, const QByteArray &contents) +{ + QMutexLocker locker(&m_mutex); + m_blacklist.whitelist(path); + m_fileCache[path] = contents; + if (path == m_path) { + m_contents = contents; + m_result = File; + m_waitCondition.wakeOne(); + } +} + +void QQmlPreviewFileLoader::directory(const QString &path, const QStringList &entries) +{ + QMutexLocker locker(&m_mutex); + m_blacklist.whitelist(path); + m_directoryCache[path] = entries; + if (path == m_path) { + m_entries = entries; + m_result = Directory; + m_waitCondition.wakeOne(); + } +} + +void QQmlPreviewFileLoader::error(const QString &path) +{ + QMutexLocker locker(&m_mutex); + m_blacklist.blacklist(path); + if (path == m_path) { + m_result = Fallback; + m_waitCondition.wakeOne(); + } +} + +void QQmlPreviewFileLoader::clearCache() +{ + QMutexLocker locker(&m_mutex); + m_fileCache.clear(); + m_directoryCache.clear(); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h new file mode 100644 index 0000000000..0c55c48c4a --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileloader.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWFILELOADER_H +#define QQMLPREVIEWFILELOADER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewblacklist.h" + +#include <QtCore/qobject.h> +#include <QtCore/qthread.h> +#include <QtCore/qmutex.h> +#include <QtCore/qwaitcondition.h> +#include <QtCore/qvector.h> +#include <QtCore/qurl.h> +#include <QtCore/qpointer.h> +#include <QtCore/qset.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewServiceImpl; +class QQmlPreviewFileLoader : public QObject +{ + Q_OBJECT +public: + enum Result { + File, + Directory, + Fallback, + Unknown + }; + + QQmlPreviewFileLoader(QQmlPreviewServiceImpl *service); + ~QQmlPreviewFileLoader(); + + Result load(const QString &file); + QByteArray contents(); + QStringList entries(); + + void whitelist(const QUrl &url); + bool isBlacklisted(const QString &file); + +signals: + void request(const QString &file); + +private: + QMutex m_mutex; + QWaitCondition m_waitCondition; + + QThread m_thread; + QPointer<QQmlPreviewServiceImpl> m_service; + + QString m_path; + QByteArray m_contents; + QStringList m_entries; + Result m_result; + + QQmlPreviewBlacklist m_blacklist; + QHash<QString, QByteArray> m_fileCache; + QHash<QString, QStringList> m_directoryCache; + + void file(const QString &file, const QByteArray &contents); + void directory(const QString &file, const QStringList &entries); + void error(const QString &file); + void clearCache(); +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWFILELOADER_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp new file mode 100644 index 0000000000..5bd96af582 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.cpp @@ -0,0 +1,472 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewhandler.h" + +#include <QtCore/qtimer.h> +#include <QtCore/qsettings.h> +#include <QtCore/qlibraryinfo.h> +#include <QtCore/qtranslator.h> + +#include <QtGui/qwindow.h> +#include <QtGui/qguiapplication.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuick/qquickitem.h> +#include <QtQml/qqmlcomponent.h> + +#include <private/qquickpixmapcache_p.h> +#include <private/qquickview_p.h> +#include <private/qhighdpiscaling_p.h> + +QT_BEGIN_NAMESPACE + +struct QuitLockDisabler +{ + const bool quitLockEnabled; + + QuitLockDisabler() : quitLockEnabled(QCoreApplication::isQuitLockEnabled()) + { + QCoreApplication::setQuitLockEnabled(false); + } + + ~QuitLockDisabler() + { + QCoreApplication::setQuitLockEnabled(quitLockEnabled); + } +}; + +QQmlPreviewHandler::QQmlPreviewHandler(QObject *parent) : QObject(parent) +{ + m_dummyItem.reset(new QQuickItem); + + // TODO: Is there a better way to determine this? We want to keep the window alive when possible + // as otherwise it will reappear in a different place when (re)loading a file. However, + // the file we load might create another window, in which case the eglfs plugin (and + // others?) will do a qFatal as it only supports a single window. + const QString platformName = QGuiApplication::platformName(); + m_supportsMultipleWindows = (platformName == QStringLiteral("windows") + || platformName == QStringLiteral("cocoa") + || platformName == QStringLiteral("xcb") + || platformName == QStringLiteral("wayland")); + + QCoreApplication::instance()->installEventFilter(this); + + m_fpsTimer.setInterval(1000); + connect(&m_fpsTimer, &QTimer::timeout, this, &QQmlPreviewHandler::fpsTimerHit); +} + +QQmlPreviewHandler::~QQmlPreviewHandler() +{ + removeTranslators(); + clear(); +} + +static void closeAllWindows() +{ + const QWindowList windows = QGuiApplication::allWindows(); + for (QWindow *window : windows) + window->close(); +} + +bool QQmlPreviewHandler::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::Show) { + if (QWindow *window = qobject_cast<QQuickWindow*>(obj)) { + m_lastPosition.initLastSavedWindowPosition(window); + } + } + if (m_currentWindow && (event->type() == QEvent::Move || event->type() == QEvent::Resize) && + qobject_cast<QQuickWindow*>(obj) == m_currentWindow) { + // we always start with factor 1 so calculate and save the origin as it would be not scaled + m_lastPosition.setPosition(m_currentWindow->framePosition() * + QHighDpiScaling::factor(m_currentWindow)); + } + + return QObject::eventFilter(obj, event); +} + +void QQmlPreviewHandler::addEngine(QQmlEngine *qmlEngine) +{ + m_engines.append(qmlEngine); +} + +void QQmlPreviewHandler::removeEngine(QQmlEngine *qmlEngine) +{ + const bool found = m_engines.removeOne(qmlEngine); + Q_ASSERT(found); + for (QObject *obj : m_createdObjects) + if (obj && QtQml::qmlEngine(obj) == qmlEngine) + delete obj; + m_createdObjects.removeAll(nullptr); +} + +void QQmlPreviewHandler::loadUrl(const QUrl &url) +{ + QSharedPointer<QuitLockDisabler> disabler(new QuitLockDisabler); + + clear(); + m_component.reset(nullptr); + QQuickPixmap::purgeCache(); + + const int numEngines = m_engines.count(); + if (numEngines > 1) { + emit error(QString::fromLatin1("%1 QML engines available. We cannot decide which one " + "should load the component.").arg(numEngines)); + return; + } else if (numEngines == 0) { + emit error(QLatin1String("No QML engines found.")); + return; + } + m_lastPosition.loadWindowPositionSettings(url); + + QQmlEngine *engine = m_engines.front(); + engine->clearComponentCache(); + m_component.reset(new QQmlComponent(engine, url, this)); + + auto onStatusChanged = [disabler, this](QQmlComponent::Status status) { + switch (status) { + case QQmlComponent::Null: + case QQmlComponent::Loading: + return true; // try again later + case QQmlComponent::Ready: + tryCreateObject(); + break; + case QQmlComponent::Error: + emit error(m_component->errorString()); + break; + default: + Q_UNREACHABLE(); + break; + } + + disconnect(m_component.data(), &QQmlComponent::statusChanged, this, nullptr); + return false; // we're done + }; + + if (onStatusChanged(m_component->status())) + connect(m_component.data(), &QQmlComponent::statusChanged, this, onStatusChanged); +} + +void QQmlPreviewHandler::rerun() +{ + if (m_component.isNull() || !m_component->isReady()) + emit error(QLatin1String("Component is not ready.")); + + QuitLockDisabler disabler; + Q_UNUSED(disabler); + clear(); + tryCreateObject(); +} + +void QQmlPreviewHandler::zoom(qreal newFactor) +{ + if (!m_currentWindow) + return; + if (qFuzzyIsNull(newFactor)) { + emit error(QString::fromLatin1("Zooming with factor: %1 will result in nothing " \ + "so it will be ignored.").arg(newFactor)); + return; + } + QString errorMessage; + bool resetZoom = false; + + if (newFactor < 0) { + resetZoom = true; + newFactor = 1.0; + } + + // On single-window devices we allow any scale factor as the window will adapt to the screen. + if (m_supportsMultipleWindows) { + const QSize newAvailableScreenSize = QQmlPreviewPosition::currentScreenSize(m_currentWindow) + * QHighDpiScaling::factor(m_currentWindow) / newFactor; + if (m_currentWindow->size().width() > newAvailableScreenSize.width()) { + errorMessage = QString::fromLatin1( + "Zooming with factor: " + "%1 will result in a too wide preview.").arg(newFactor); + } + if (m_currentWindow->size().height() > newAvailableScreenSize.height()) { + errorMessage = QString::fromLatin1( + "Zooming with factor: " + "%1 will result in a too heigh preview.").arg(newFactor); + } + } + + if (errorMessage.isEmpty()) { + const QPoint newToOriginMappedPosition = m_currentWindow->position() * + QHighDpiScaling::factor(m_currentWindow) / newFactor; + m_currentWindow->destroy(); + QHighDpiScaling::setScreenFactor(m_currentWindow->screen(), newFactor); + if (resetZoom) + QHighDpiScaling::updateHighDpiScaling(); + m_currentWindow->setPosition(newToOriginMappedPosition); + m_currentWindow->show(); + } else { + emit error(errorMessage); + } +} + +void QQmlPreviewHandler::removeTranslators() +{ + if (!m_qtTranslator.isNull()) { + QCoreApplication::removeTranslator(m_qtTranslator.get()); + m_qtTranslator.reset(); + } + + if (m_qmlTranslator.isNull()) { + QCoreApplication::removeTranslator(m_qmlTranslator.get()); + m_qmlTranslator.reset(); + } +} + +void QQmlPreviewHandler::language(const QUrl &context, const QLocale &locale) +{ + removeTranslators(); + + m_qtTranslator.reset(new QTranslator(this)); + if (m_qtTranslator->load(locale, QLatin1String("qt"), QLatin1String("_"), + QLibraryInfo::location(QLibraryInfo::TranslationsPath))) { + QCoreApplication::installTranslator(m_qtTranslator.get()); + } + + m_qmlTranslator.reset(new QTranslator(this)); + if (m_qmlTranslator->load(locale, QLatin1String("qml"), QLatin1String("_"), + context.toLocalFile() + QLatin1String("/i18n"))) { + QCoreApplication::installTranslator(m_qmlTranslator.get()); + } + + for (QQmlEngine *engine : qAsConst(m_engines)) + engine->retranslate(); +} + +void QQmlPreviewHandler::clear() +{ + qDeleteAll(m_createdObjects); + m_createdObjects.clear(); + setCurrentWindow(nullptr); +} + +Qt::WindowFlags fixFlags(Qt::WindowFlags flags) +{ + // If only the type flag is given, some other window flags are automatically assumed. When we + // add a flag, we need to make those explicit. + switch (flags) { + case Qt::Window: + return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint + | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint; + case Qt::Dialog: + case Qt::Tool: + return flags | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint; + default: + return flags; + } +} + +void QQmlPreviewHandler::showObject(QObject *object) +{ + if (QWindow *window = qobject_cast<QWindow *>(object)) { + setCurrentWindow(qobject_cast<QQuickWindow *>(window)); + for (QWindow *otherWindow : QGuiApplication::allWindows()) { + if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(otherWindow)) { + if (quickWindow == m_currentWindow) + continue; + quickWindow->setVisible(false); + quickWindow->setFlags(quickWindow->flags() & ~Qt::WindowStaysOnTopHint); + } + } + } else if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) { + setCurrentWindow(nullptr); + for (QWindow *window : QGuiApplication::allWindows()) { + if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window)) { + if (m_currentWindow != nullptr) { + emit error(QLatin1String("Multiple QQuickWindows available. We cannot " + "decide which one to use.")); + return; + } + setCurrentWindow(quickWindow); + } else { + window->setVisible(false); + window->setFlag(Qt::WindowStaysOnTopHint, false); + } + } + + if (m_currentWindow == nullptr) { + setCurrentWindow(new QQuickWindow); + m_createdObjects.append(m_currentWindow.data()); + } + + for (QQuickItem *oldItem : m_currentWindow->contentItem()->childItems()) + oldItem->setParentItem(m_dummyItem.data()); + + // Special case for QQuickView, as that keeps a "root" pointer around, and uses it to + // automatically resize the window or the item. + if (QQuickView *view = qobject_cast<QQuickView *>(m_currentWindow)) + QQuickViewPrivate::get(view)->setRootObject(item); + else + item->setParentItem(m_currentWindow->contentItem()); + + m_currentWindow->resize(item->size().toSize()); + } else { + emit error(QLatin1String("Created object is neither a QWindow nor a QQuickItem.")); + } + + if (m_currentWindow) { + m_lastPosition.initLastSavedWindowPosition(m_currentWindow); + m_currentWindow->setFlags(fixFlags(m_currentWindow->flags()) | Qt::WindowStaysOnTopHint); + m_currentWindow->setVisible(true); + } +} + +void QQmlPreviewHandler::setCurrentWindow(QQuickWindow *window) +{ + if (window == m_currentWindow.data()) + return; + + if (m_currentWindow) { + disconnect(m_currentWindow.data(), &QQuickWindow::beforeSynchronizing, + this, &QQmlPreviewHandler::beforeSynchronizing); + disconnect(m_currentWindow.data(), &QQuickWindow::afterSynchronizing, + this, &QQmlPreviewHandler::afterSynchronizing); + disconnect(m_currentWindow.data(), &QQuickWindow::beforeRendering, + this, &QQmlPreviewHandler::beforeRendering); + disconnect(m_currentWindow.data(), &QQuickWindow::frameSwapped, + this, &QQmlPreviewHandler::frameSwapped); + m_fpsTimer.stop(); + m_rendering = FrameTime(); + m_synchronizing = FrameTime(); + } + + m_currentWindow = window; + + if (m_currentWindow) { + connect(m_currentWindow.data(), &QQuickWindow::beforeSynchronizing, + this, &QQmlPreviewHandler::beforeSynchronizing, Qt::DirectConnection); + connect(m_currentWindow.data(), &QQuickWindow::afterSynchronizing, + this, &QQmlPreviewHandler::afterSynchronizing, Qt::DirectConnection); + connect(m_currentWindow.data(), &QQuickWindow::beforeRendering, + this, &QQmlPreviewHandler::beforeRendering, Qt::DirectConnection); + connect(m_currentWindow.data(), &QQuickWindow::frameSwapped, + this, &QQmlPreviewHandler::frameSwapped, Qt::DirectConnection); + m_fpsTimer.start(); + } +} + +void QQmlPreviewHandler::beforeSynchronizing() +{ + m_synchronizing.beginFrame(); +} + +void QQmlPreviewHandler::afterSynchronizing() +{ + + if (m_rendering.elapsed >= 0) + m_rendering.endFrame(); + m_synchronizing.recordFrame(); + m_synchronizing.endFrame(); +} + +void QQmlPreviewHandler::beforeRendering() +{ + m_rendering.beginFrame(); +} + +void QQmlPreviewHandler::frameSwapped() +{ + m_rendering.recordFrame(); +} + +void QQmlPreviewHandler::FrameTime::beginFrame() +{ + timer.start(); +} + +void QQmlPreviewHandler::FrameTime::recordFrame() +{ + elapsed = timer.elapsed(); +} + +void QQmlPreviewHandler::FrameTime::endFrame() +{ + if (elapsed < min) + min = static_cast<quint16>(qMax(0ll, elapsed)); + if (elapsed > max) + max = static_cast<quint16>(qMin(qint64(std::numeric_limits<quint16>::max()), elapsed)); + total = static_cast<quint16>(qBound(0ll, qint64(std::numeric_limits<quint16>::max()), + elapsed + total)); + ++number; + elapsed = -1; +} + +void QQmlPreviewHandler::FrameTime::reset() +{ + min = std::numeric_limits<quint16>::max(); + max = 0; + total = 0; + number = 0; +} + +void QQmlPreviewHandler::fpsTimerHit() +{ + const FpsInfo info = { + m_synchronizing.number, + m_synchronizing.min, + m_synchronizing.max, + m_synchronizing.total, + + m_rendering.number, + m_rendering.min, + m_rendering.max, + m_rendering.total + }; + + emit fps(info); + + m_rendering.reset(); + m_synchronizing.reset(); +} + +void QQmlPreviewHandler::tryCreateObject() +{ + if (!m_supportsMultipleWindows) + closeAllWindows(); + QObject *object = m_component->create(); + m_createdObjects.append(object); + showObject(object); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h new file mode 100644 index 0000000000..21ea672580 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewhandler.h @@ -0,0 +1,154 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWHANDLER_H +#define QQMLPREVIEWHANDLER_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewposition.h" + +#include <QtCore/qobject.h> +#include <QtCore/qvector.h> +#include <QtCore/qrect.h> +#include <QtCore/qpointer.h> +#include <QtCore/qelapsedtimer.h> +#include <QtQml/qqmlengine.h> + +QT_BEGIN_NAMESPACE + +class QQmlEngine; +class QQuickItem; +class QQmlPreviewUrlInterceptor; +class QQuickWindow; +class QTranslator; + +class QQmlPreviewHandler : public QObject +{ + Q_OBJECT +public: + explicit QQmlPreviewHandler(QObject *parent = nullptr); + ~QQmlPreviewHandler(); + + void addEngine(QQmlEngine *engine); + void removeEngine(QQmlEngine *engine); + + void loadUrl(const QUrl &url); + void rerun(); + void zoom(qreal newFactor); + void language(const QUrl &context, const QLocale &locale); + + void clear(); + + struct FpsInfo { + quint16 numSyncs; + quint16 minSync; + quint16 maxSync; + quint16 totalSync; + + quint16 numRenders; + quint16 minRender; + quint16 maxRender; + quint16 totalRender; + }; + +signals: + void error(const QString &message); + void fps(const FpsInfo &info); + +protected: + bool eventFilter(QObject *obj, QEvent *event); +private: + void tryCreateObject(); + void showObject(QObject *object); + void setCurrentWindow(QQuickWindow *window); + + void beforeSynchronizing(); + void afterSynchronizing(); + void beforeRendering(); + void frameSwapped(); + + void fpsTimerHit(); + void removeTranslators(); + + QScopedPointer<QQuickItem> m_dummyItem; + QList<QQmlEngine *> m_engines; + QVector<QPointer<QObject>> m_createdObjects; + QScopedPointer<QQmlComponent> m_component; + QPointer<QQuickWindow> m_currentWindow; + bool m_supportsMultipleWindows; + QQmlPreviewPosition m_lastPosition; + + QTimer m_fpsTimer; + + struct FrameTime { + void beginFrame(); + void recordFrame(); + void endFrame(); + void reset(); + + QElapsedTimer timer; + qint64 elapsed = -1; + quint16 min = std::numeric_limits<quint16>::max(); + quint16 max = 0; + quint16 total = 0; + quint16 number = 0; + }; + + FrameTime m_rendering; + FrameTime m_synchronizing; + + QScopedPointer<QTranslator> m_qtTranslator; + QScopedPointer<QTranslator> m_qmlTranslator; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QQmlPreviewHandler::FpsInfo) + +#endif // QQMLPREVIEWHANDLER_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp new file mode 100644 index 0000000000..3edcbac0a9 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.cpp @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewposition.h" + +#include <QtGui/qwindow.h> +#include <QtGui/qscreen.h> +#include <QtGui/qguiapplication.h> + +QT_BEGIN_NAMESPACE + +static const QSize availableScreenSize(const QPoint &point) +{ + if (const QScreen *screen = QGuiApplication::screenAt(point)) + return screen->availableGeometry().size(); + return QSize(); +} + +QQmlPreviewPosition::QQmlPreviewPosition() + : m_settings("QtProject", "QtQmlPreview") +{ + m_savePositionTimer.setSingleShot(true); + m_savePositionTimer.setInterval(500); + QObject::connect(&m_savePositionTimer, &QTimer::timeout, [this]() { + saveWindowPosition(); + }); +} + +void QQmlPreviewPosition::setPosition(const QPoint &point) +{ + m_hasPosition = true; + m_lastWindowPosition = point; + m_savePositionTimer.start(); +} + +void QQmlPreviewPosition::saveWindowPosition() +{ + if (m_hasPosition) { + if (!m_settingsKey.isNull()) + m_settings.setValue(m_settingsKey, m_lastWindowPosition); + + m_settings.setValue(QLatin1String("global_lastpostion"), m_lastWindowPosition); + } +} + +void QQmlPreviewPosition::loadWindowPositionSettings(const QUrl &url) +{ + m_settingsKey = url.toString(QUrl::PreferLocalFile) + QLatin1String("_lastpostion"); + + if (m_settings.contains(m_settingsKey)) { + m_hasPosition = true; + m_lastWindowPosition = m_settings.value(m_settingsKey).toPoint(); + } +} + +void QQmlPreviewPosition::initLastSavedWindowPosition(QWindow *window) +{ + if (m_positionedWindows.contains(window)) + return; + if (!m_hasPosition) { + // in case there was nothing saved, we do not want to set anything + if (!m_settings.contains(QLatin1String("global_lastpostion"))) + return; + m_lastWindowPosition = m_settings.value(QLatin1String("global_lastpostion")).toPoint(); + } + if (QGuiApplication::screenAt(m_lastWindowPosition)) + window->setFramePosition(m_lastWindowPosition); + + m_positionedWindows.append(window); +} + +const QSize QQmlPreviewPosition::currentScreenSize(QWindow *window) +{ + return availableScreenSize(window->position()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h new file mode 100644 index 0000000000..3d4ca9dc67 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewposition.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWPOSITION_H +#define QQMLPREVIEWPOSITION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qvector.h> +#include <QtCore/qpoint.h> +#include <QtCore/qurl.h> +#include <QtCore/qtimer.h> +#include <QtCore/qsettings.h> + +QT_BEGIN_NAMESPACE + +class QWindow; + +class QQmlPreviewPosition +{ +public: + QQmlPreviewPosition(); + + void setPosition(const QPoint &point); + void saveWindowPosition(); + void loadWindowPositionSettings(const QUrl &url); + void initLastSavedWindowPosition(QWindow *window); + static const QSize currentScreenSize(QWindow *window); + +private: + bool m_hasPosition = false; + QPoint m_lastWindowPosition; + QSettings m_settings; + QString m_settingsKey; + QTimer m_savePositionTimer; + QVector<QWindow *> m_positionedWindows; +}; + + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWPOSITION_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp new file mode 100644 index 0000000000..2e2224df47 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.cpp @@ -0,0 +1,195 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewservice.h" + +#include <QtCore/qpointer.h> +#include <QtQml/qqmlengine.h> +#include <QtQml/qqmlcomponent.h> +#include <QtQuick/qquickwindow.h> +#include <QtQuick/qquickitem.h> +#include <QtGui/qguiapplication.h> + +#include <private/qquickpixmapcache_p.h> +#include <private/qqmldebugconnector_p.h> +#include <private/qversionedpacket_p.h> + +QT_BEGIN_NAMESPACE + +const QString QQmlPreviewServiceImpl::s_key = QStringLiteral("QmlPreview"); +using QQmlDebugPacket = QVersionedPacket<QQmlDebugConnector>; + +QQmlPreviewServiceImpl::QQmlPreviewServiceImpl(QObject *parent) : + QQmlDebugService(s_key, 1.0f, parent) +{ + m_loader.reset(new QQmlPreviewFileLoader(this)); + connect(this, &QQmlPreviewServiceImpl::load, + m_loader.data(), &QQmlPreviewFileLoader::whitelist, Qt::DirectConnection); + connect(this, &QQmlPreviewServiceImpl::load, &m_handler, &QQmlPreviewHandler::loadUrl); + connect(this, &QQmlPreviewServiceImpl::rerun, &m_handler, &QQmlPreviewHandler::rerun); + connect(this, &QQmlPreviewServiceImpl::zoom, &m_handler, &QQmlPreviewHandler::zoom); + connect(this, &QQmlPreviewServiceImpl::language, &m_handler, &QQmlPreviewHandler::language); + connect(&m_handler, &QQmlPreviewHandler::error, this, &QQmlPreviewServiceImpl::forwardError, + Qt::DirectConnection); + connect(&m_handler, &QQmlPreviewHandler::fps, this, &QQmlPreviewServiceImpl::forwardFps, + Qt::DirectConnection); +} + +QQmlPreviewServiceImpl::~QQmlPreviewServiceImpl() +{ +} + +void QQmlPreviewServiceImpl::messageReceived(const QByteArray &data) +{ + QQmlDebugPacket packet(data); + qint8 command; + + packet >> command; + switch (command) { + case File: { + QString path; + QByteArray contents; + packet >> path >> contents; + emit file(path, contents); + + // Replace the whole scene with the first file successfully loaded over the debug + // connection. This is an OK approximation of the root component, and if the client wants + // something specific, it will send an explicit Load anyway. + if (m_currentUrl.isEmpty() && path.endsWith(".qml")) { + if (path.startsWith(':')) + m_currentUrl = QUrl("qrc" + path); + else + m_currentUrl = QUrl::fromLocalFile(path); + emit load(m_currentUrl); + } + break; + } + case Directory: { + QString path; + QStringList entries; + packet >> path >> entries; + emit directory(path, entries); + break; + } + case Load: { + QUrl url; + packet >> url; + if (url.isEmpty()) + url = m_currentUrl; + else + m_currentUrl = url; + emit load(url); + break; + } + case Error: { + QString file; + packet >> file; + emit error(file); + break; + } + case Rerun: + emit rerun(); + break; + case ClearCache: + emit clearCache(); + break; + case Zoom: { + float factor; + packet >> factor; + emit zoom(static_cast<qreal>(factor)); + break; + } + case Language: { + QUrl context; + QString locale; + packet >> context >> locale; + emit language(context.isEmpty() ? m_currentUrl : context, + locale.isEmpty() ? QLocale() : QLocale(locale)); + break; + } + default: + forwardError(QString::fromLatin1("Invalid command: %1").arg(command)); + break; + } +} + +void QQmlPreviewServiceImpl::engineAboutToBeAdded(QJSEngine *engine) +{ + if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine)) + m_handler.addEngine(qmlEngine); + emit attachedToEngine(engine); +} + +void QQmlPreviewServiceImpl::engineAboutToBeRemoved(QJSEngine *engine) +{ + if (QQmlEngine *qmlEngine = qobject_cast<QQmlEngine *>(engine)) + m_handler.removeEngine(qmlEngine); + emit detachedFromEngine(engine); +} + +void QQmlPreviewServiceImpl::stateChanged(QQmlDebugService::State state) +{ + m_fileEngine.reset(state == Enabled ? new QQmlPreviewFileEngineHandler(m_loader.data()) + : nullptr); +} + +void QQmlPreviewServiceImpl::forwardRequest(const QString &file) +{ + QQmlDebugPacket packet; + packet << static_cast<qint8>(Request) << file; + emit messageToClient(name(), packet.data()); +} + +void QQmlPreviewServiceImpl::forwardError(const QString &error) +{ + QQmlDebugPacket packet; + packet << static_cast<qint8>(Error) << error; + emit messageToClient(name(), packet.data()); +} + +void QQmlPreviewServiceImpl::forwardFps(const QQmlPreviewHandler::FpsInfo &frames) +{ + QQmlDebugPacket packet; + packet << static_cast<qint8>(Fps) + << frames.numSyncs << frames.minSync << frames.maxSync << frames.totalSync + << frames.numRenders << frames.minRender << frames.maxRender << frames.totalRender; + emit messageToClient(name(), packet.data()); +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h new file mode 100644 index 0000000000..7bdc87ec59 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWSERVICE_H +#define QQMLPREVIEWSERVICE_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qqmlpreviewhandler.h" +#include "qqmlpreviewfileengine.h" +#include <private/qqmldebugserviceinterfaces_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewFileEngineHandler; +class QQmlPreviewHandler; +class QQmlPreviewServiceImpl : public QQmlDebugService +{ + Q_OBJECT + +public: + enum Command { + File, + Load, + Request, + Error, + Rerun, + Directory, + ClearCache, + Zoom, + Fps, + Language + }; + + static const QString s_key; + + QQmlPreviewServiceImpl(QObject *parent = nullptr); + virtual ~QQmlPreviewServiceImpl(); + + void messageReceived(const QByteArray &message) override; + void engineAboutToBeAdded(QJSEngine *engine) override; + void engineAboutToBeRemoved(QJSEngine *engine) override; + void stateChanged(State state) override; + + void forwardRequest(const QString &file); + void forwardError(const QString &error); + void forwardFps(const QQmlPreviewHandler::FpsInfo &frames); + +signals: + void error(const QString &file); + void file(const QString &file, const QByteArray &contents); + void directory(const QString &file, const QStringList &entries); + void load(const QUrl &url); + void rerun(); + void clearCache(); + void zoom(qreal factor); + void language(const QUrl &context, const QLocale &locale); + +private: + QScopedPointer<QQmlPreviewFileEngineHandler> m_fileEngine; + QScopedPointer<QQmlPreviewFileLoader> m_loader; + QQmlPreviewHandler m_handler; + QUrl m_currentUrl; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWSERVICE_H diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json new file mode 100644 index 0000000000..d7e1ef1f10 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservice.json @@ -0,0 +1,3 @@ +{ + "Keys" : [ "QmlPreview" ] +} diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp new file mode 100644 index 0000000000..f0aa3226c8 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.cpp @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qqmlpreviewservicefactory.h" +#include "qqmlpreviewservice.h" + +QT_BEGIN_NAMESPACE + +QQmlDebugService *QQmlPreviewServiceFactory::create(const QString &key) +{ + return key == QQmlPreviewServiceImpl::s_key ? new QQmlPreviewServiceImpl(this) : nullptr; +} + +QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h new file mode 100644 index 0000000000..0ceadf24f5 --- /dev/null +++ b/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewservicefactory.h @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2018 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QML preview debug service. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QQMLPREVIEWSERVCIEFACTORY_H +#define QQMLPREVIEWSERVCIEFACTORY_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qqmldebugservicefactory_p.h> + +QT_BEGIN_NAMESPACE + +class QQmlPreviewServiceFactory : public QQmlDebugServiceFactory +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID QQmlDebugServiceFactory_iid FILE "qqmlpreviewservice.json") + +public: + QQmlDebugService *create(const QString &key) override; +}; + +QT_END_NAMESPACE + +#endif // QQMLPREVIEWSERVCIEFACTORY_H diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp index 19104927f2..a688e98b3f 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.cpp @@ -80,8 +80,7 @@ void QQmlProfilerAdapter::init(QQmlProfilerService *service, QQmlProfiler *profi // convert to QByteArrays that can be sent to the debug client static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, QQmlProfiler::LocationHash &locations, - QList<QByteArray> &messages, - bool trackLocations) + QList<QByteArray> &messages) { QQmlDebugPacket ds; Q_ASSERT_X((d.messageType & (1 << 31)) == 0, Q_FUNC_INFO, @@ -96,7 +95,7 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, if (decodedMessageType == QQmlProfilerDefinitions::RangeEnd || decodedMessageType == QQmlProfilerDefinitions::RangeStart) { ds << d.time << decodedMessageType << static_cast<quint32>(d.detailType); - if (trackLocations && d.locationId != 0) + if (d.locationId != 0) ds << static_cast<qint64>(d.locationId); } else { auto i = locations.find(d.locationId); @@ -107,8 +106,7 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, << static_cast<qint32>(i->location.column); if (d.messageType & (1 << QQmlProfilerDefinitions::RangeData)) { // Send both, location and data ... - if (trackLocations) - ds << static_cast<qint64>(d.locationId); + ds << static_cast<qint64>(d.locationId); messages.append(ds.squeezedData()); ds.clear(); ds << d.time << int(QQmlProfilerDefinitions::RangeData) @@ -116,10 +114,8 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, << (i->location.sourceFile.isEmpty() ? i->url.toString() : i->location.sourceFile); } - if (trackLocations) { - ds << static_cast<qint64>(d.locationId); - locations.erase(i); // ... so that we can erase here without missing anything. - } + ds << static_cast<qint64>(d.locationId); + locations.erase(i); // ... so that we can erase here without missing anything. } else { // Skip RangeData and RangeLocation: We've already sent them continue; @@ -130,14 +126,13 @@ static void qQmlProfilerDataToByteArrays(const QQmlProfilerData &d, } } -qint64 QQmlProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) +qint64 QQmlProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) { while (next != data.length()) { const QQmlProfilerData &nextData = data.at(next); if (nextData.time > until || messages.length() > s_numMessagesPerBatch) return nextData.time; - qQmlProfilerDataToByteArrays(nextData, locations, messages, trackLocations); + qQmlProfilerDataToByteArrays(nextData, locations, messages); ++next; } diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h index b14b72d254..12544a19c2 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofileradapter.h @@ -61,8 +61,7 @@ class QQmlProfilerAdapter : public QQmlAbstractProfilerAdapter { public: QQmlProfilerAdapter(QQmlProfilerService *service, QQmlEnginePrivate *engine); QQmlProfilerAdapter(QQmlProfilerService *service, QQmlTypeLoader *loader); - qint64 sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) override; + qint64 sendMessages(qint64 until, QList<QByteArray> &messages) override; void receiveData(const QVector<QQmlProfilerData> &new_data, const QQmlProfiler::LocationHash &locations); diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp index 21a5663f59..462401a093 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.cpp @@ -58,7 +58,7 @@ Q_QML_DEBUG_PLUGIN_LOADER(QQmlAbstractProfilerAdapter) QQmlProfilerServiceImpl::QQmlProfilerServiceImpl(QObject *parent) : QQmlConfigurableDebugService<QQmlProfilerService>(1, parent), - m_waitingForStop(false), m_useMessageTypes(false), m_globalEnabled(false), m_globalFeatures(0) + m_waitingForStop(false), m_globalEnabled(false), m_globalFeatures(0) { m_timer.start(); QQmlAbstractProfilerAdapter *quickAdapter = @@ -332,7 +332,7 @@ void QQmlProfilerServiceImpl::stopProfiling(QJSEngine *engine) m_waitingForStop = true; for (QQmlAbstractProfilerAdapter *profiler : qAsConst(reporting)) - profiler->reportData(m_useMessageTypes); + profiler->reportData(); for (QQmlAbstractProfilerAdapter *profiler : qAsConst(stopping)) profiler->stopProfiling(); @@ -367,8 +367,7 @@ void QQmlProfilerServiceImpl::sendMessages() m_startTimes.erase(m_startTimes.begin()); qint64 next = first->sendMessages(m_startTimes.isEmpty() ? std::numeric_limits<qint64>::max() : - m_startTimes.begin().key(), messages, - m_useMessageTypes); + m_startTimes.begin().key(), messages); if (next != -1) m_startTimes.insert(next, first); @@ -454,13 +453,15 @@ void QQmlProfilerServiceImpl::messageReceived(const QByteArray &message) &m_flushTimer, &QTimer::stop); } } + + bool useMessageTypes = false; if (!stream.atEnd()) - stream >> m_useMessageTypes; + stream >> useMessageTypes; // If engineId == -1 objectForId() and then the cast will return 0. - if (enabled) + if (enabled && useMessageTypes) // If the client doesn't support message types don't profile. startProfiling(qobject_cast<QJSEngine *>(objectForId(engineId)), features); - else + else if (!enabled) // On stopProfiling the client doesn't repeat useMessageTypes. stopProfiling(qobject_cast<QJSEngine *>(objectForId(engineId))); stopWaiting(); @@ -486,7 +487,7 @@ void QQmlProfilerServiceImpl::flush() } for (QQmlAbstractProfilerAdapter *profiler : qAsConst(reporting)) - profiler->reportData(m_useMessageTypes); + profiler->reportData(); } QT_END_NAMESPACE diff --git a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h index 2b92a478c1..3791ab29ae 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qqmlprofilerservice.h @@ -117,7 +117,6 @@ private: QElapsedTimer m_timer; QTimer m_flushTimer; bool m_waitingForStop; - bool m_useMessageTypes; bool m_globalEnabled; quint64 m_globalFeatures; diff --git a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp index f1ac8ef998..12c36f3dd6 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp +++ b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.cpp @@ -87,14 +87,17 @@ qint64 QV4ProfilerAdapter::appendMemoryEvents(qint64 until, QList<QByteArray> &m qint64 QV4ProfilerAdapter::finalizeMessages(qint64 until, QList<QByteArray> &messages, qint64 callNext, QQmlDebugPacket &d) { + qint64 memoryNext = -1; + if (callNext == -1) { m_functionLocations.clear(); m_functionCallData.clear(); m_functionCallPos = 0; + memoryNext = appendMemoryEvents(until, messages, d); + } else { + memoryNext = appendMemoryEvents(qMin(callNext, until), messages, d); } - qint64 memoryNext = appendMemoryEvents(until, messages, d); - if (memoryNext == -1) { m_memoryData.clear(); m_memoryPos = 0; @@ -104,8 +107,7 @@ qint64 QV4ProfilerAdapter::finalizeMessages(qint64 until, QList<QByteArray> &mes return callNext == -1 ? memoryNext : qMin(callNext, memoryNext); } -qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) +qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) { QQmlDebugPacket d; @@ -134,24 +136,17 @@ qint64 QV4ProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &message appendMemoryEvents(props.start, messages, d); auto location = m_functionLocations.find(props.id); - d << props.start << int(RangeStart) << int(Javascript); - if (trackLocations) - d << static_cast<qint64>(props.id); + d << props.start << int(RangeStart) << int(Javascript) << static_cast<qint64>(props.id); if (location != m_functionLocations.end()) { messages.push_back(d.squeezedData()); d.clear(); d << props.start << int(RangeLocation) << int(Javascript) << location->file << location->line - << location->column; - if (trackLocations) - d << static_cast<qint64>(props.id); + << location->column << static_cast<qint64>(props.id); messages.push_back(d.squeezedData()); d.clear(); - d << props.start << int(RangeData) << int(Javascript) << location->name; - - if (trackLocations) { - d << static_cast<qint64>(props.id); - m_functionLocations.erase(location); - } + d << props.start << int(RangeData) << int(Javascript) << location->name + << static_cast<qint64>(props.id); + m_functionLocations.erase(location); } messages.push_back(d.squeezedData()); d.clear(); diff --git a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h index 2211c82fc5..c4ca38d9b0 100644 --- a/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h +++ b/src/plugins/qmltooling/qmldbg_profiler/qv4profileradapter.h @@ -68,8 +68,7 @@ class QV4ProfilerAdapter : public QQmlAbstractProfilerAdapter { public: QV4ProfilerAdapter(QQmlProfilerService *service, QV4::ExecutionEngine *engine); - virtual qint64 sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) override; + virtual qint64 sendMessages(qint64 until, QList<QByteArray> &messages) override; void receiveData(const QV4::Profiling::FunctionLocationHash &, const QVector<QV4::Profiling::FunctionCallProperties> &, diff --git a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp index 2c152e4cd5..79a1c82411 100644 --- a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp +++ b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.cpp @@ -152,10 +152,8 @@ static void qQuickProfilerDataToByteArrays(const QQuickProfilerData &data, } } -qint64 QQuickProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages, - bool trackLocations) +qint64 QQuickProfilerAdapter::sendMessages(qint64 until, QList<QByteArray> &messages) { - Q_UNUSED(trackLocations); while (next < m_data.size()) { if (m_data[next].time <= until && messages.length() <= s_numMessagesPerBatch) qQuickProfilerDataToByteArrays(m_data[next++], messages); diff --git a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h index 1ad020afd6..1f3467c1d0 100644 --- a/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h +++ b/src/plugins/qmltooling/qmldbg_quickprofiler/qquickprofileradapter.h @@ -61,7 +61,7 @@ class QQuickProfilerAdapter : public QQmlAbstractProfilerAdapter { public: QQuickProfilerAdapter(QObject *parent = 0); ~QQuickProfilerAdapter(); - qint64 sendMessages(qint64 until, QList<QByteArray> &messages, bool trackLocations) override; + qint64 sendMessages(qint64 until, QList<QByteArray> &messages) override; void receiveData(const QVector<QQuickProfilerData> &new_data); private: diff --git a/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp index c1e86f0b3c..8293e88038 100644 --- a/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp +++ b/src/plugins/qmltooling/qmldbg_server/qqmldebugserver.cpp @@ -177,14 +177,13 @@ private: void changeServiceState(const QString &serviceName, QQmlDebugService::State state); void removeThread(); void receiveMessage(); - void invalidPacket(); + void protocolError(); QQmlDebugServerConnection *m_connection; QHash<QString, QQmlDebugService *> m_plugins; QStringList m_clientPlugins; bool m_gotHello; bool m_blockingMode; - bool m_clientSupportsMultiPackets; QHash<QJSEngine *, EngineCondition> m_engineConditions; @@ -277,8 +276,7 @@ static void cleanupOnShutdown() QQmlDebugServerImpl::QQmlDebugServerImpl() : m_connection(nullptr), m_gotHello(false), - m_blockingMode(false), - m_clientSupportsMultiPackets(false) + m_blockingMode(false) { static bool postRoutineAdded = false; if (!postRoutineAdded) { @@ -464,10 +462,9 @@ void QQmlDebugServerImpl::receiveMessage() s_dataStreamVersion = QDataStream::Qt_DefaultCompiledVersion; } + bool clientSupportsMultiPackets = false; if (!in.atEnd()) - in >> m_clientSupportsMultiPackets; - else - m_clientSupportsMultiPackets = false; + in >> clientSupportsMultiPackets; // Send the hello answer immediately, since it needs to arrive before // the plugins below start sending messages. @@ -475,13 +472,15 @@ void QQmlDebugServerImpl::receiveMessage() QQmlDebugPacket out; QStringList pluginNames; QList<float> pluginVersions; - const int count = m_plugins.count(); - pluginNames.reserve(count); - pluginVersions.reserve(count); - for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin(); - i != m_plugins.constEnd(); ++i) { - pluginNames << i.key(); - pluginVersions << i.value()->version(); + if (clientSupportsMultiPackets) { // otherwise, disable all plugins + const int count = m_plugins.count(); + pluginNames.reserve(count); + pluginVersions.reserve(count); + for (QHash<QString, QQmlDebugService *>::ConstIterator i = m_plugins.constBegin(); + i != m_plugins.constEnd(); ++i) { + pluginNames << i.key(); + pluginVersions << i.value()->version(); + } } out << QString(QStringLiteral("QDeclarativeDebugClient")) << 0 << protocolVersion @@ -523,7 +522,7 @@ void QQmlDebugServerImpl::receiveMessage() } else { qWarning("QML Debugger: Invalid control message %d.", op); - invalidPacket(); + protocolError(); return; } @@ -701,16 +700,11 @@ void QQmlDebugServerImpl::sendMessage(const QString &name, const QByteArray &mes void QQmlDebugServerImpl::sendMessages(const QString &name, const QList<QByteArray> &messages) { if (canSendMessage(name)) { - if (m_clientSupportsMultiPackets) { - QQmlDebugPacket out; - out << name; - for (const QByteArray &message : messages) - out << message; - m_protocol->send(out.data()); - } else { - for (const QByteArray &message : messages) - doSendMessage(name, message); - } + QQmlDebugPacket out; + out << name; + for (const QByteArray &message : messages) + out << message; + m_protocol->send(out.data()); m_connection->flush(); } } @@ -743,16 +737,16 @@ void QQmlDebugServerImpl::setDevice(QIODevice *socket) m_protocol = new QPacketProtocol(socket, this); QObject::connect(m_protocol, &QPacketProtocol::readyRead, this, &QQmlDebugServerImpl::receiveMessage); - QObject::connect(m_protocol, &QPacketProtocol::invalidPacket, - this, &QQmlDebugServerImpl::invalidPacket); + QObject::connect(m_protocol, &QPacketProtocol::error, + this, &QQmlDebugServerImpl::protocolError); if (blockingMode()) m_protocol->waitForReadyRead(-1); } -void QQmlDebugServerImpl::invalidPacket() +void QQmlDebugServerImpl::protocolError() { - qWarning("QML Debugger: Received a corrupted packet! Giving up ..."); + qWarning("QML Debugger: A protocol error has occurred! Giving up ..."); m_connection->disconnect(); // protocol might still be processing packages at this point m_protocol->deleteLater(); diff --git a/src/plugins/qmltooling/qmltooling.pro b/src/plugins/qmltooling/qmltooling.pro index 119415372b..30097be77b 100644 --- a/src/plugins/qmltooling/qmltooling.pro +++ b/src/plugins/qmltooling/qmltooling.pro @@ -6,9 +6,8 @@ SUBDIRS += \ packetprotocol # Connectors -SUBDIRS += \ - qmldbg_native \ - qmldbg_server +SUBDIRS += qmldbg_native +qtConfig(thread): SUBDIRS += qmldbg_server qmldbg_native.depends = packetprotocol qmldbg_server.depends = packetprotocol @@ -36,6 +35,10 @@ qtHaveModule(quick) { SUBDIRS += \ qmldbg_inspector \ qmldbg_quickprofiler + + qtConfig(qml-network): SUBDIRS += qmldbg_preview + qmldbg_inspector.depends = packetprotocol qmldbg_quickprofiler.depends = packetprotocol + qmldbg_preview.depends = packetprotocol } diff --git a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp index 0bd51cbf46..d728686248 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp +++ b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.cpp @@ -176,6 +176,12 @@ void QSGOpenVGInternalRectangleNode::setGradientStops(const QGradientStops &stop m_fillDirty = true; } +void QSGOpenVGInternalRectangleNode::setGradientVertical(bool vertical) +{ + m_vertical = vertical; + m_fillDirty = true; +} + void QSGOpenVGInternalRectangleNode::setRadius(qreal radius) { m_radius = radius; @@ -242,13 +248,13 @@ void QSGOpenVGInternalRectangleNode::render() } else { // Linear Gradient vgSetParameteri(m_rectanglePaint, VG_PAINT_TYPE, VG_PAINT_TYPE_LINEAR_GRADIENT); - const VGfloat verticalLinearGradient[] = { - 0.0f, + const VGfloat linearGradient[] = { 0.0f, 0.0f, - static_cast<VGfloat>(m_rect.height()) + m_vertical ? 0.0f : static_cast<VGfloat>(m_rect.width()), + m_vertical ? static_cast<VGfloat>(m_rect.height()) : 0.0f }; - vgSetParameterfv(m_rectanglePaint, VG_PAINT_LINEAR_GRADIENT, 4, verticalLinearGradient); + vgSetParameterfv(m_rectanglePaint, VG_PAINT_LINEAR_GRADIENT, 4, linearGradient); vgSetParameteri(m_rectanglePaint, VG_PAINT_COLOR_RAMP_SPREAD_MODE, VG_COLOR_RAMP_SPREAD_PAD); vgSetParameteri(m_rectanglePaint, VG_PAINT_COLOR_RAMP_PREMULTIPLIED, false); diff --git a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h index e8d25c94f8..86d2c3318c 100644 --- a/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h +++ b/src/plugins/scenegraph/openvg/qsgopenvginternalrectanglenode.h @@ -59,6 +59,7 @@ public: void setPenColor(const QColor &color) override; void setPenWidth(qreal width) override; void setGradientStops(const QGradientStops &stops) override; + void setGradientVertical(bool vertical) override; void setRadius(qreal radius) override; void setAligned(bool aligned) override; void update() override; @@ -85,6 +86,7 @@ private: qreal m_penWidth = 0.0; qreal m_radius = 0.0; bool m_aligned = false; + bool m_vertical = true; QGradientStops m_gradientStops; VGPath m_rectanglePath; |