Merge pull request #11613 from jherico/feature/qml_whitelist

Support for custom functionality for specific QML URLs
This commit is contained in:
Andrew Meadows 2017-10-17 17:34:22 -07:00 committed by GitHub
commit 25818fc5e9
2 changed files with 85 additions and 17 deletions

View file

@ -41,6 +41,7 @@
#include <gl/OffscreenGLCanvas.h> #include <gl/OffscreenGLCanvas.h>
#include <gl/GLHelpers.h> #include <gl/GLHelpers.h>
#include <gl/Context.h> #include <gl/Context.h>
#include <shared/ReadWriteLockable.h>
#include "types/FileTypeProfile.h" #include "types/FileTypeProfile.h"
#include "types/HFWebEngineProfile.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(trace_render_qml_gl, "trace.render.qml.gl")
Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus")
class OffscreenQmlWhitelist : public Dependency, private ReadWriteLockable {
SINGLETON_DEPENDENCY
public:
void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback) {
withWriteLock([&] {
for (const auto& url : urls) {
_callbacks[url].push_back(callback);
}
});
}
QList<QmlContextCallback> getCallbacksForUrl(const QUrl& url) const {
return resultWithReadLock<QList<QmlContextCallback>>([&] {
QList<QmlContextCallback> result;
auto itr = _callbacks.find(url);
if (_callbacks.end() != itr) {
result = *itr;
}
return result;
});
}
private:
QHash<QUrl, QList<QmlContextCallback>> _callbacks;
};
QSharedPointer<OffscreenQmlWhitelist> getQmlWhitelist() {
static std::once_flag once;
std::call_once(once, [&] {
DependencyManager::set<OffscreenQmlWhitelist>();
});
return DependencyManager::get<OffscreenQmlWhitelist>();
}
void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback) {
getQmlWhitelist()->addWhitelistContextHandler(urls, callback);
}
QmlContextCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {};
struct TextureSet { struct TextureSet {
// The number of surfaces with this size // The number of surfaces with this size
size_t count { 0 }; size_t count { 0 };
@ -640,18 +688,26 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl); _qmlContext->setBaseUrl(baseUrl);
} }
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) { void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback) {
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Called load on a non-surface thread"; qCWarning(uiLogging) << "Called load on a non-surface thread";
} }
// Synchronous loading may take a while; restart the deadlock timer // Synchronous loading may take a while; restart the deadlock timer
QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection);
// Get any whitelist functionality
QList<QmlContextCallback> 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; QQmlContext* targetContext = _qmlContext;
if (_rootItem && createNewContext) { if (_rootItem && createNewContext) {
targetContext = new QQmlContext(targetContext); targetContext = new QQmlContext(targetContext);
} }
// FIXME eliminate loading of relative file paths for QML
QUrl finalQmlSource = qmlSource; QUrl finalQmlSource = qmlSource;
if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) {
finalQmlSource = _qmlContext->resolvedUrl(qmlSource); 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); auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) { if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this, connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
[this, qmlComponent, targetContext, onQmlLoadedCallback](QQmlComponent::Status) { finishQmlLoad(qmlComponent, targetContext, callbacks);
finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback);
}); });
return; return;
} }
finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback); finishQmlLoad(qmlComponent, targetContext, callbacks);
} }
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) { void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
load(qmlSource, true, onQmlLoadedCallback); load(qmlSource, true, onQmlLoadedCallback);
} }
void OffscreenQmlSurface::load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) { void OffscreenQmlSurface::load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback) {
load(qmlSource, false, onQmlLoadedCallback); load(qmlSource, false, onQmlLoadedCallback);
} }
void OffscreenQmlSurface::load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback) {
return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
}
void OffscreenQmlSurface::clearCache() { void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache(); _qmlContext->engine()->clearComponentCache();
} }
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback) { void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) { if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) { 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 // Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears // Call this before qmlComponent->completeCreate() otherwise ghost window appears
if (newItem && _rootItem) { if (newItem && _rootItem) {
onQmlLoadedCallback(qmlContext, newObject); for (const auto& callback : callbacks) {
callback(qmlContext, newObject);
}
} }
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>(); QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
@ -751,8 +812,11 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
_rootItem = newItem; _rootItem = newItem;
_rootItem->setParentItem(_quickWindow->contentItem()); _rootItem->setParentItem(_quickWindow->contentItem());
_rootItem->setSize(_quickWindow->renderTargetSize()); _rootItem->setSize(_quickWindow->renderTargetSize());
// Call this callback after rootitem is set, otherwise VrMenu wont work // 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() { void OffscreenQmlSurface::updateQuick() {

View file

@ -35,12 +35,18 @@ class QQuickItem;
// one copy in flight, and one copy being used by the receiver // one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3 #define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*, QObject*)>;
class OffscreenQmlSurface : public QObject { class OffscreenQmlSurface : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged)
public: public:
static void setSharedContext(QOpenGLContext* context); static void setSharedContext(QOpenGLContext* context);
static QmlContextCallback DEFAULT_CONTEXT_CALLBACK;
static void addWhitelistContextHandler(const std::initializer_list<QUrl>& urls, const QmlContextCallback& callback);
static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); };
OffscreenQmlSurface(); OffscreenQmlSurface();
virtual ~OffscreenQmlSurface(); virtual ~OffscreenQmlSurface();
@ -50,12 +56,10 @@ public:
void resize(const QSize& size, bool forceResize = false); void resize(const QSize& size, bool forceResize = false);
QSize size() const; QSize size() const;
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {}); Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {}); Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {}); Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QString& qmlSourceFile, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback = [](QQmlContext*, QObject*) {}) { Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
return load(QUrl(qmlSourceFile), onQmlLoadedCallback);
}
void clearCache(); void clearCache();
void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; } void setMaxFps(uint8_t maxFps) { _maxFps = maxFps; }
// Optional values for event handling // Optional values for event handling
@ -120,7 +124,7 @@ protected:
private: private:
static QOpenGLContext* getSharedContext(); static QOpenGLContext* getSharedContext();
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function<void(QQmlContext*, QObject*)> onQmlLoadedCallback); void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QList<QmlContextCallback>& callbacks);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo(); void setupFbo();
bool allowNewFrame(uint8_t fps); bool allowNewFrame(uint8_t fps);