| 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 | |