From f53a74b5155849b8ea7ffc1fd6dbcff6698c2521 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Tue, 17 Oct 2017 11:53:30 -0700 Subject: [PATCH] Support for custom functionality for specific QML URLs --- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 84 ++++++++++++++++++--- libraries/ui/src/ui/OffscreenQmlSurface.h | 18 +++-- 2 files changed, 85 insertions(+), 17 deletions(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 6cf8a927ff..bdcbd63c5a 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include "types/FileTypeProfile.h" #include "types/HFWebEngineProfile.h" @@ -52,6 +53,53 @@ Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") + +class OffscreenQmlWhitelist : public Dependency, private ReadWriteLockable { + SINGLETON_DEPENDENCY + +public: + + void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback) { + withWriteLock([&] { + for (const auto& url : urls) { + _callbacks[url].push_back(callback); + } + }); + } + + QList getCallbacksForUrl(const QUrl& url) const { + return resultWithReadLock>([&] { + QList result; + auto itr = _callbacks.find(url); + if (_callbacks.end() != itr) { + result = *itr; + } + return result; + }); + } + +private: + + QHash> _callbacks; +}; + +QSharedPointer getQmlWhitelist() { + static std::once_flag once; + std::call_once(once, [&] { + DependencyManager::set(); + }); + + return DependencyManager::get(); +} + + +void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback) { + getQmlWhitelist()->addWhitelistContextHandler(urls, callback); +} + + +QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {}; + struct TextureSet { // The number of surfaces with this size size_t count { 0 }; @@ -640,18 +688,26 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { _qmlContext->setBaseUrl(baseUrl); } -void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) { if (QThread::currentThread() != thread()) { qCWarning(uiLogging) << "Called load on a non-surface thread"; } // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); + // Get any whitelist functionality + QList callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource); + // If we have whitelisted content, we must load a new context + createNewContext |= !callbacks.empty(); + callbacks.push_back(onQmlLoadedCallback); + QQmlContext* targetContext = _qmlContext; if (_rootItem && createNewContext) { targetContext = new QQmlContext(targetContext); } + + // FIXME eliminate loading of relative file paths for QML QUrl finalQmlSource = qmlSource; if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { finalQmlSource = _qmlContext->resolvedUrl(qmlSource); @@ -659,29 +715,32 @@ void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); if (qmlComponent->isLoading()) { - connect(qmlComponent, &QQmlComponent::statusChanged, this, - [this, qmlComponent, targetContext, onQmlLoadedCallback](QQmlComponent::Status) { - finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback); + connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { + finishQmlLoad(qmlComponent, targetContext, callbacks); }); return; } - finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback); + finishQmlLoad(qmlComponent, targetContext, callbacks); } -void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function onQmlLoadedCallback) { +void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { load(qmlSource, true, onQmlLoadedCallback); } -void OffscreenQmlSurface::load(const QUrl& qmlSource, std::function onQmlLoadedCallback) { +void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) { load(qmlSource, false, onQmlLoadedCallback); } +void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) { + return load(QUrl(qmlSourceFile), onQmlLoadedCallback); +} + void OffscreenQmlSurface::clearCache() { _qmlContext->engine()->clearComponentCache(); } -void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function onQmlLoadedCallback) { +void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks) { disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); if (qmlComponent->isError()) { for (const auto& error : qmlComponent->errors()) { @@ -716,7 +775,9 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext // Make sure we will call callback for this codepath // Call this before qmlComponent->completeCreate() otherwise ghost window appears if (newItem && _rootItem) { - onQmlLoadedCallback(qmlContext, newObject); + for (const auto& callback : callbacks) { + callback(qmlContext, newObject); + } } QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); @@ -751,8 +812,11 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext _rootItem = newItem; _rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setSize(_quickWindow->renderTargetSize()); + // Call this callback after rootitem is set, otherwise VrMenu wont work - onQmlLoadedCallback(qmlContext, newObject); + for (const auto& callback : callbacks) { + callback(qmlContext, newObject); + } } void OffscreenQmlSurface::updateQuick() { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 990f81848d..74eafe9f13 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -35,12 +35,18 @@ class QQuickItem; // one copy in flight, and one copy being used by the receiver #define GPU_RESOURCE_BUFFER_SIZE 3 +using QmlContextCallback = std::function; + class OffscreenQmlSurface : public QObject { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: static void setSharedContext(QOpenGLContext* context); + static QmlContextCallback DEFAULT_CONTEXT_CALLBACK; + static void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback); + static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; + OffscreenQmlSurface(); virtual ~OffscreenQmlSurface(); @@ -50,12 +56,10 @@ public: void resize(const QSize& size, bool forceResize = false); QSize size() const; - Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, std::function onQmlLoadedCallback = [](QQmlContext*, QObject*) {}); - Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, std::function onQmlLoadedCallback = [](QQmlContext*, QObject*) {}); - Q_INVOKABLE void load(const QUrl& qmlSource, std::function onQmlLoadedCallback = [](QQmlContext*, QObject*) {}); - Q_INVOKABLE void load(const QString& qmlSourceFile, std::function onQmlLoadedCallback = [](QQmlContext*, QObject*) {}) { - return load(QUrl(qmlSourceFile), onQmlLoadedCallback); - } + Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK); void clearCache(); void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } // Optional values for event handling @@ -120,7 +124,7 @@ protected: private: static QOpenGLContext* getSharedContext(); - void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function onQmlLoadedCallback); + void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList& callbacks); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); void setupFbo(); bool allowNewFrame(uint8_t fps);