1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Milian Wolff <milian.wolff@kdab.com> |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWebChannel module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qqmlwebchannel.h" |
41 | |
42 | #include "qwebchannel_p.h" |
43 | #include "qmetaobjectpublisher_p.h" |
44 | #include "qwebchannelabstracttransport.h" |
45 | |
46 | #include <QtQml/QQmlContext> |
47 | |
48 | #include "qqmlwebchannelattached_p.h" |
49 | |
50 | QT_BEGIN_NAMESPACE |
51 | |
52 | /*! |
53 | \qmltype WebChannel |
54 | \instantiates QQmlWebChannel |
55 | |
56 | \inqmlmodule QtWebChannel |
57 | \ingroup webchannel-qml |
58 | \brief QML interface to QWebChannel. |
59 | \since 5.4 |
60 | |
61 | The WebChannel provides a mechanism to transparently access QObject or QML objects from HTML |
62 | clients. All properties, signals and public slots can be used from the HTML clients. |
63 | |
64 | \sa QWebChannel, {Qt WebChannel JavaScript API}{JavaScript API} |
65 | */ |
66 | |
67 | /*! |
68 | \qmlproperty QQmlListProperty<QObject> WebChannel::transports |
69 | A list of transport objects, which implement QWebChannelAbstractTransport. The transports |
70 | are used to talk to the remote clients. |
71 | |
72 | \sa connectTo(), disconnectFrom() |
73 | */ |
74 | |
75 | /*! |
76 | \qmlproperty QQmlListProperty<QObject> WebChannel::registeredObjects |
77 | |
78 | \brief A list of objects which should be accessible to remote clients. |
79 | |
80 | The objects must have the attached \l id property set to an identifier, under which the |
81 | object is then known on the HTML side. |
82 | |
83 | Once registered, all signals and property changes are automatically propagated to the clients. |
84 | Public invokable methods, including slots, are also accessible to the clients. |
85 | |
86 | If one needs to register objects which are not available when the component is created, use the |
87 | imperative registerObjects method. |
88 | |
89 | \sa registerObjects(), id |
90 | */ |
91 | |
92 | class QQmlWebChannelPrivate : public QWebChannelPrivate |
93 | { |
94 | Q_DECLARE_PUBLIC(QQmlWebChannel) |
95 | public: |
96 | QVector<QObject*> registeredObjects; |
97 | |
98 | void _q_objectIdChanged(const QString &newId); |
99 | }; |
100 | |
101 | /*! |
102 | \internal |
103 | |
104 | Update the name of the sender object, when its attached WebChannel.id property changed. |
105 | This is required, since during startup the property is empty and only gets set later on. |
106 | */ |
107 | void QQmlWebChannelPrivate::_q_objectIdChanged(const QString &newId) |
108 | { |
109 | Q_Q(QQmlWebChannel); |
110 | const QQmlWebChannelAttached *const attached = qobject_cast<QQmlWebChannelAttached*>(object: q->sender()); |
111 | Q_ASSERT(attached); |
112 | Q_ASSERT(attached->parent()); |
113 | Q_ASSERT(registeredObjects.contains(attached->parent())); |
114 | |
115 | QObject *const object = attached->parent(); |
116 | const QString &oldId = publisher->registeredObjectIds.value(key: object); |
117 | |
118 | if (!oldId.isEmpty()) { |
119 | q->deregisterObject(object); |
120 | } |
121 | |
122 | q->registerObject(id: newId, object); |
123 | } |
124 | |
125 | QQmlWebChannel::QQmlWebChannel(QObject *parent) |
126 | : QWebChannel(*(new QQmlWebChannelPrivate), parent) |
127 | { |
128 | } |
129 | |
130 | QQmlWebChannel::~QQmlWebChannel() |
131 | { |
132 | |
133 | } |
134 | |
135 | /*! |
136 | \qmlmethod void WebChannel::registerObjects(QVariantMap objects) |
137 | Registers the specified \a objects to make them accessible to HTML clients. |
138 | The key of the map is used as an identifier for the object on the client side. |
139 | |
140 | Once registered, all signals and property changes are automatically propagated to the clients. |
141 | Public invokable methods, including slots, are also accessible to the clients. |
142 | |
143 | This imperative API can be used to register objects on the fly. For static objects, the declarative |
144 | registeredObjects property should be preferred. |
145 | |
146 | \sa registeredObjects |
147 | */ |
148 | void QQmlWebChannel::registerObjects(const QVariantMap &objects) |
149 | { |
150 | Q_D(QQmlWebChannel); |
151 | QMap<QString, QVariant>::const_iterator it = objects.constBegin(); |
152 | for (; it != objects.constEnd(); ++it) { |
153 | QObject *object = it.value().value<QObject*>(); |
154 | if (!object) { |
155 | qWarning(msg: "Invalid QObject given to register under name %s" , qPrintable(it.key())); |
156 | continue; |
157 | } |
158 | d->publisher->registerObject(id: it.key(), object); |
159 | } |
160 | } |
161 | |
162 | QQmlWebChannelAttached *QQmlWebChannel::qmlAttachedProperties(QObject *obj) |
163 | { |
164 | return new QQmlWebChannelAttached(obj); |
165 | } |
166 | |
167 | /*! |
168 | \qmlmethod void WebChannel::connectTo(QWebChannelAbstractTransport transport) |
169 | |
170 | \brief Connects to the \a transport, which represents a communication |
171 | channel to a single client. |
172 | |
173 | The transport object must be an implementation of QWebChannelAbstractTransport. |
174 | |
175 | \sa transports, disconnectFrom() |
176 | */ |
177 | void QQmlWebChannel::connectTo(QObject *transport) |
178 | { |
179 | if (QWebChannelAbstractTransport *realTransport = qobject_cast<QWebChannelAbstractTransport*>(object: transport)) { |
180 | QWebChannel::connectTo(transport: realTransport); |
181 | } else { |
182 | qWarning() << "Cannot connect to transport" << transport << " - it is not a QWebChannelAbstractTransport." ; |
183 | } |
184 | } |
185 | |
186 | /*! |
187 | \qmlmethod void WebChannel::disconnectFrom(QWebChannelAbstractTransport transport) |
188 | |
189 | \brief Disconnects the \a transport from this WebChannel. |
190 | |
191 | The client will not be able to communicate with the WebChannel anymore, nor will it receive any |
192 | signals or property updates. |
193 | |
194 | \sa connectTo() |
195 | */ |
196 | void QQmlWebChannel::disconnectFrom(QObject *transport) |
197 | { |
198 | if (QWebChannelAbstractTransport *realTransport = qobject_cast<QWebChannelAbstractTransport*>(object: transport)) { |
199 | QWebChannel::disconnectFrom(transport: realTransport); |
200 | } else { |
201 | qWarning() << "Cannot disconnect from transport" << transport << " - it is not a QWebChannelAbstractTransport." ; |
202 | } |
203 | } |
204 | |
205 | QQmlListProperty<QObject> QQmlWebChannel::registeredObjects() |
206 | { |
207 | return QQmlListProperty<QObject>(this, 0, |
208 | registeredObjects_append, |
209 | registeredObjects_count, |
210 | registeredObjects_at, |
211 | registeredObjects_clear); |
212 | } |
213 | |
214 | void QQmlWebChannel::registeredObjects_append(QQmlListProperty<QObject> *prop, QObject *object) |
215 | { |
216 | const QQmlWebChannelAttached *const attached = qobject_cast<QQmlWebChannelAttached*>( |
217 | object: qmlAttachedPropertiesObject<QQmlWebChannel>(obj: object, create: false /* don't create */)); |
218 | if (!attached) { |
219 | const QQmlContext *const context = qmlContext(object); |
220 | qWarning() << "Cannot register object" << context->nameForObject(object) << '(' << object << ") without attached WebChannel.id property. Did you forget to set it?" ; |
221 | return; |
222 | } |
223 | QQmlWebChannel *channel = static_cast<QQmlWebChannel*>(prop->object); |
224 | if (!attached->id().isEmpty()) { |
225 | // TODO: warning in such cases? |
226 | channel->registerObject(id: attached->id(), object); |
227 | } |
228 | channel->d_func()->registeredObjects.append(t: object); |
229 | connect(sender: attached, SIGNAL(idChanged(QString)), receiver: channel, SLOT(_q_objectIdChanged(QString))); |
230 | } |
231 | |
232 | int QQmlWebChannel::registeredObjects_count(QQmlListProperty<QObject> *prop) |
233 | { |
234 | return static_cast<QQmlWebChannel*>(prop->object)->d_func()->registeredObjects.size(); |
235 | } |
236 | |
237 | QObject *QQmlWebChannel::registeredObjects_at(QQmlListProperty<QObject> *prop, int index) |
238 | { |
239 | return static_cast<QQmlWebChannel*>(prop->object)->d_func()->registeredObjects.at(i: index); |
240 | } |
241 | |
242 | void QQmlWebChannel::registeredObjects_clear(QQmlListProperty<QObject> *prop) |
243 | { |
244 | QQmlWebChannel *channel = static_cast<QQmlWebChannel*>(prop->object); |
245 | foreach (QObject *object, channel->d_func()->registeredObjects) { |
246 | channel->deregisterObject(object); |
247 | } |
248 | return channel->d_func()->registeredObjects.clear(); |
249 | } |
250 | |
251 | QQmlListProperty<QObject> QQmlWebChannel::transports() |
252 | { |
253 | return QQmlListProperty<QObject>(this, 0, |
254 | transports_append, |
255 | transports_count, |
256 | transports_at, |
257 | transports_clear); |
258 | } |
259 | |
260 | void QQmlWebChannel::transports_append(QQmlListProperty<QObject> *prop, QObject *transport) |
261 | { |
262 | QQmlWebChannel *channel = static_cast<QQmlWebChannel*>(prop->object); |
263 | channel->connectTo(transport); |
264 | } |
265 | |
266 | int QQmlWebChannel::transports_count(QQmlListProperty<QObject> *prop) |
267 | { |
268 | return static_cast<QQmlWebChannel*>(prop->object)->d_func()->transports.size(); |
269 | } |
270 | |
271 | QObject *QQmlWebChannel::transports_at(QQmlListProperty<QObject> *prop, int index) |
272 | { |
273 | QQmlWebChannel *channel = static_cast<QQmlWebChannel*>(prop->object); |
274 | return channel->d_func()->transports.at(i: index); |
275 | } |
276 | |
277 | void QQmlWebChannel::transports_clear(QQmlListProperty<QObject> *prop) |
278 | { |
279 | QWebChannel *channel = static_cast<QWebChannel*>(prop->object); |
280 | foreach (QWebChannelAbstractTransport *transport, channel->d_func()->transports) { |
281 | channel->disconnectFrom(transport); |
282 | } |
283 | Q_ASSERT(channel->d_func()->transports.isEmpty()); |
284 | } |
285 | |
286 | QT_END_NAMESPACE |
287 | |
288 | #include "moc_qqmlwebchannel.cpp" |
289 | |