Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qqmlinplacepreviewhandler.cpp
Go to the documentation of this file.
1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant
4
7
8#include <QtGui/qguiapplication.h>
9#include <QtQml/qqmlcomponent.h>
10#include <QtQuick/qquickwindow.h>
11#include <QtQuick/qquickitem.h>
12
13#include <private/qqmlmetatype_p.h>
14#include <private/qv4compileddata_p.h>
15#include <private/qqmlpreviewobjectpatch_p.h>
16#include <private/qv4mm_p.h>
17#include <private/qqmlcomponent_p.h>
18#include <private/qv4resolvedtypereference_p.h>
19
21
27
29{
30 InplaceUpdate(QQmlInPlacePreviewHandler *handler, QQmlEngine *engine)
31 : engine(engine), handler(handler)
32 {
33 }
34
35 QQmlEngine *engine = nullptr;
39
40 void emitReloadFailure(const QString &message) { emit handler->hotReloadFailure(message); }
41
42 void emitError(const QString &message) { emit handler->error(message); }
43
44private:
45 // Sending a signal is fine from a different thread. Anything else not so much.
46 QQmlInPlacePreviewHandler *handler = nullptr;
47};
48
49QQmlInPlacePreviewHandler::QQmlInPlacePreviewHandler(QObject *parent) : QQmlPreviewHandler(parent)
50{
51}
52
53QQmlInPlacePreviewHandler::~QQmlInPlacePreviewHandler() = default;
54
55void QQmlInPlacePreviewHandler::connectToService(QQmlPreviewServiceImpl *service)
56{
58 connect(this, &QQmlInPlacePreviewHandler::hotReloadFailure, service,
59 &QQmlPreviewServiceImpl::forwardHotReloadFailure, Qt::DirectConnection);
60 connect(service, &QQmlPreviewServiceImpl::drop, this, [this](const QUrl &url) {
61 if (!m_droppedUrls.contains(url))
62 m_droppedUrls.push_back(url);
63 });
64 connect(service, &QQmlPreviewServiceImpl::rerun, this, [this]() {
65 emit error(QLatin1String("You cannot rerun if in-place updates are enabled."));
66 });
67 connect(service, &QQmlPreviewServiceImpl::zoom, this, [this](qreal zoomFactor) {
68 QQmlPreviewPosition position;
69 zoomWindow(currentWindow(), zoomFactor, &position);
70 });
71}
72
73QQuickWindow *findCurrentWindow()
74{
75 QQuickWindow *found = nullptr;
76 for (QWindow *window : QGuiApplication::allWindows()) {
77 if (QQuickWindow *quickWindow = qobject_cast<QQuickWindow *>(window)) {
78 if (std::exchange(found, quickWindow))
79 return nullptr; // Multiple windows available. We can't decide
80 }
81 }
82
83 return found;
84}
85
86void findCurrentRootObject(QQmlEngine *engine, const QUrl &url, QQmlPreviewHandler *receiver)
87{
88 // Schedule this on the engine's thread
89 QMetaObject::invokeMethod(engine, [engine, url, receiver]() {
90 QV4::ExecutionEngine *v4 = engine->handle();
91 const QQmlRefPointer<QV4::ExecutableCompilationUnit> unit = v4->compilationUnitForUrl(url);
92 if (!unit)
93 return;
94
95 const std::vector<QObject *> objects =
96 v4->memoryManager->findObjectsForCompilationUnits({ unit->baseCompilationUnit() });
97 QPointer<QObject> found;
98 for (QObject *object : objects) {
99 QQmlData *ddata = QQmlData::get(object);
100 if (ddata && ddata->compilationUnit == unit && ddata->cuObjectIndex == 0) {
101 if (std::exchange(found, object))
102 return; // Multiple candidates. We can't decide
103 }
104 }
105
106 // Schedule reply on the debug server thread
107 QMetaObject::invokeMethod(receiver, [receiver, found]() {
108 if (QQuickItem *rootItem = qobject_cast<QQuickItem *>(found)) {
109 receiver->setCurrentRootItem(rootItem);
110 receiver->setCurrentWindow(findCurrentWindow());
111 return;
112 }
113
114 if (QQuickWindow *window = qobject_cast<QQuickWindow *>(found)) {
115 receiver->setCurrentWindow(window);
116 receiver->setCurrentRootItem(nullptr);
117 return;
118 }
119
120 receiver->setCurrentRootItem(nullptr);
121 receiver->setCurrentWindow(findCurrentWindow());
122 });
123 });
124}
125
126// Walk all compilation units in the engine and replace resolved type references
127// that still point to any of the dropped (old) base CUs with freshly compiled ones.
128// TODO: This is dangerous. We are manipulating compilation units exposed to multiple
129// engines on potentially multiple threads.
131 QV4::ExecutionEngine *v4,
132 const QList<QQmlRefPointer<QV4::CompiledData::CompilationUnit>> &droppedUnits)
133{
134 const auto allCUs = v4->compilationUnits();
135 for (const auto &ecu : allCUs) {
136 const auto &baseCU = ecu->baseCompilationUnit();
137 for (auto *typeRef : std::as_const(baseCU->resolvedTypes)) {
138 if (typeRef->isSelfReference())
139 continue;
140 const auto &refCU = typeRef->compilationUnit();
141 if (!refCU)
142 continue;
143 for (const auto &oldCU : droppedUnits) {
144 if (refCU == oldCU) {
145 const auto newCU = QQmlMetaType::obtainCompilationUnit(oldCU->finalUrl());
146 if (newCU)
147 typeRef->setCompilationUnit(newCU);
148 break;
149 }
150 }
151 }
152 }
153}
154
155static void updateInplace(QQmlComponent *component, std::shared_ptr<InplaceUpdate> inplaceUpdate)
156{
157 ComponentUpdate componentUpdate;
158 const auto componentUpdateIt =
159 std::find_if(inplaceUpdate->pendingComponentUpdates.begin(),
160 inplaceUpdate->pendingComponentUpdates.end(),
161 [component](const ComponentUpdate &engineUpdate) {
162 return engineUpdate.component.get() == component;
163 });
164
165 if (componentUpdateIt == inplaceUpdate->pendingComponentUpdates.end())
166 return;
167
168 componentUpdate = std::move(*componentUpdateIt);
169 inplaceUpdate->pendingComponentUpdates.erase(componentUpdateIt);
170
171 switch (component->status()) {
172 case QQmlComponent::Null:
173 case QQmlComponent::Loading:
174 Q_UNREACHABLE_RETURN();
175 case QQmlComponent::Ready:
176 break;
177 case QQmlComponent::Error:
178 inplaceUpdate->emitError(component->errorString());
179 return;
180 }
181
182 QV4::ExecutionEngine *v4 = component->engine()->handle();
183
184 std::vector<QObject *> objects;
185 QV4::CompiledData::CompilationUnitDiff diff;
186 if (const auto oldExecCU = componentUpdate.oldUnit) {
187 const auto newExecCU = QQmlComponentPrivate::get(component)->compilationUnit();
188
189 objects = v4->memoryManager->findObjectsForCompilationUnits(
190 { oldExecCU->baseCompilationUnit() });
191 diff = QV4::CompiledData::diffCompilationUnits(oldExecCU->unitData(),
192 newExecCU->unitData());
193 if (!QQmlPreview::applyDiff(objects, diff, oldExecCU, newExecCU))
194 inplaceUpdate->emitReloadFailure("Could not apply diff");
195
196 inplaceUpdate->processedComponentUpdates.push_back(std::move(componentUpdate));
197 }
198
199 // Once all components have been handled, update resolved type references in all
200 // compilation units and refresh all bindings
201 if (inplaceUpdate->pendingComponentUpdates.empty()) {
202 updateResolvedTypeReferences(component->engine()->handle(), inplaceUpdate->droppedUnits);
203 for (const ComponentUpdate &update : inplaceUpdate->processedComponentUpdates)
204 QQmlPreview::refreshBindings(
205 update.oldUnit,
206 QQmlComponentPrivate::get(update.component.get())->compilationUnit());
207 }
208}
209
210void updateEngine(std::shared_ptr<InplaceUpdate> inplaceUpdate, const QList<QUrl> &urls)
211{
212 QV4::ExecutionEngine *v4 = inplaceUpdate->engine->handle();
213 std::vector<QQmlComponent *> components;
214 for (const QUrl &url : urls) {
215 // First remove any cached instance of the CU
216 // NB: Don't remove from the ExecutionEngine's list of CUs. We need those for the GC.
217 v4->typeLoader()->removeFromCache(url);
218
219 // Hold on to the old unit.
220 QQmlRefPointer<QV4::ExecutableCompilationUnit> oldUnit = v4->compilationUnitForUrl(url);
221
222 // Then have it re-compile with updated source code
223 inplaceUpdate->pendingComponentUpdates.push_back({
224 std::make_unique<QQmlComponent>(inplaceUpdate->engine, url),
225 std::move(oldUnit),
226 });
227
228 // Additionally store in another vector since updateInplace deletes from inplaceUpdate
229 components.push_back(inplaceUpdate->pendingComponentUpdates.back().component.get());
230 }
231
232 for (QQmlComponent *component : components) {
233 switch (component->status()) {
234 case QQmlComponent::Null:
235 case QQmlComponent::Loading:
236 QObject::connect(component, &QQmlComponent::statusChanged, component,
237 [component, inplaceUpdate]() {
238 switch (component->status()) {
239 case QQmlComponent::Ready:
240 case QQmlComponent::Error:
241 updateInplace(component, inplaceUpdate);
242 break;
243 default:
244 break;
245 }
246 });
247 break;
248 case QQmlComponent::Ready:
249 case QQmlComponent::Error:
250 updateInplace(component, inplaceUpdate);
251 break;
252 }
253 }
254}
255
256void QQmlInPlacePreviewHandler::load(const QUrl &url)
257{
258 // Find the updated objects from the URLs we were asked to drop. Those are the ones that have
259 // been updated.
260
261 const QList<QQmlEngine *> seenEngines = engines();
262 const QList<QUrl> urls = std::exchange(m_droppedUrls, {});
263
264 QList<QQmlRefPointer<QV4::CompiledData::CompilationUnit>> droppedUnits;
265 for (const QUrl &droppedUrl : urls) {
266 while (const auto cu = QQmlMetaType::obtainCompilationUnit(droppedUrl)) {
267 droppedUnits.append(cu);
268 QQmlMetaType::deepClearCompositeType(cu);
269 }
270 }
271 for (QQmlEngine *engine : seenEngines) {
272 std::shared_ptr<InplaceUpdate> inplaceUpdate =
273 std::make_shared<InplaceUpdate>(this, engine);
274 inplaceUpdate->droppedUnits = droppedUnits;
275
276 // Schedule this on the engine's thread. The shared pointer keeps the update alive as
277 // long as necessary.
278 QMetaObject::invokeMethod(engine,
279 [inplaceUpdate, urls]() { updateEngine(inplaceUpdate, urls); });
280 }
281
282 // Update current window and root item based on passed URL
283 // so that we can zoom and report FPS.
284 // We can only do that if we have an unambiguous root item or window.
285
286 if (seenEngines.size() == 1) {
287 findCurrentRootObject(seenEngines[0], url, this);
288 } else {
289 setCurrentRootItem(nullptr);
291 return;
292 }
293}
294
295QT_END_NAMESPACE
296
297#include "moc_qqmlinplacepreviewhandler.cpp"
void setCurrentRootItem(QQuickItem *item)
void setCurrentWindow(QQuickWindow *window)
virtual void connectToService(QQmlPreviewServiceImpl *service)
Combined button and popup list for selecting options.
static void updateResolvedTypeReferences(QV4::ExecutionEngine *v4, const QList< QQmlRefPointer< QV4::CompiledData::CompilationUnit > > &droppedUnits)
static void updateInplace(QQmlComponent *component, std::shared_ptr< InplaceUpdate > inplaceUpdate)
void findCurrentRootObject(QQmlEngine *engine, const QUrl &url, QQmlPreviewHandler *receiver)
QQuickWindow * findCurrentWindow()
void updateEngine(std::shared_ptr< InplaceUpdate > inplaceUpdate, const QList< QUrl > &urls)
std::unique_ptr< QQmlComponent > component
QQmlRefPointer< QV4::ExecutableCompilationUnit > oldUnit
std::vector< ComponentUpdate > pendingComponentUpdates
QList< QQmlRefPointer< QV4::CompiledData::CompilationUnit > > droppedUnits
InplaceUpdate(QQmlInPlacePreviewHandler *handler, QQmlEngine *engine)
void emitError(const QString &message)
void emitReloadFailure(const QString &message)
std::vector< ComponentUpdate > processedComponentUpdates