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