Working on QML whitelist functionality

This commit is contained in:
Brad Davis 2017-10-26 14:20:13 -07:00
parent a5caae3739
commit ffc51d5387
7 changed files with 113 additions and 62 deletions

View file

@ -0,0 +1,18 @@
import QtQuick 2.5
import QtQuick.Controls 1.4
Rectangle {
width: 100
height: 100
color: "white"
Rectangle {
width: 10
height: 10
color: "red"
}
Label {
text: OverlayWindowTestString
anchors.centerIn: parent
}
}

View file

@ -22,7 +22,6 @@ Windows.Window {
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
destroyOnCloseButton: false
property var source;
property var component;
property var dynamicContent;
// Keyboard control properties in case needed by QML content.
@ -35,28 +34,13 @@ Windows.Window {
dynamicContent.destroy();
dynamicContent = null;
}
component = Qt.createComponent(source);
console.log("Created component " + component + " from source " + source);
}
onComponentChanged: {
console.log("Component changed to " + component)
populate();
}
function populate() {
console.log("Populate called: dynamicContent " + dynamicContent + " component " + component);
if (!dynamicContent && component) {
if (component.status == Component.Error) {
console.log("Error loading component:", component.errorString());
} else if (component.status == Component.Ready) {
console.log("Building dynamic content");
dynamicContent = component.createObject(contentHolder);
} else {
console.log("Component not yet ready, connecting to status change");
component.statusChanged.connect(populate);
}
}
console.log("QQQ Foo");
QmlSurface.createContentFromQml(source, contentHolder, function(newObject) {
console.log("QQQ Bar " + dynamicContent);
dynamicContent = newObject;
dynamicContent.visible = true;
});
console.log("QQQ Baz");
}
// Handle message traffic from the script that launched us to the loaded QML

View file

@ -2240,6 +2240,12 @@ extern void setupPreferences();
void Application::initializeUi() {
// Make sure all QML surfaces share the main thread GL context
OffscreenQmlSurface::setSharedContext(_offscreenContext->getContext());
OffscreenQmlSurface::addWhitelistContextHandler(QUrl{ "qrc:///qml/OverlayWindowTest.qml" },
[](QQmlContext* context) {
qDebug() << "Whitelist OverlayWindow worked";
context->setContextProperty("OverlayWindowTestString", "TestWorked");
});
AddressBarDialog::registerType();
ErrorDialog::registerType();

View file

@ -62,7 +62,7 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) {
QUrl url { properties[SOURCE_PROPERTY].toString() };
if (url.scheme() != "http" && url.scheme() != "https" && url.scheme() != "file" && url.scheme() != "about" &&
url.scheme() != "atp") {
url.scheme() != "atp" && url.scheme() != "qrc") {
properties[SOURCE_PROPERTY] = QUrl::fromLocalFile(url.toString()).toString();
}

View file

@ -64,11 +64,19 @@ public:
for (const auto& url : urls) {
_callbacks[url].push_back(callback);
}
for (const auto& url : _callbacks.keys()) {
qDebug() << "URL found for " << url << " with " << _callbacks[url].size() << " items";
}
});
}
QList<QmlContextCallback> getCallbacksForUrl(const QUrl& url) const {
qDebug() << "Looking for callbacks for " << url;
return resultWithReadLock<QList<QmlContextCallback>>([&] {
for (const auto& url : _callbacks.keys()) {
qDebug() << "URL found for " << url << " with " << _callbacks[url].size() << " items";
}
QList<QmlContextCallback> result;
auto itr = _callbacks.find(url);
if (_callbacks.end() != itr) {
@ -98,7 +106,7 @@ void OffscreenQmlSurface::addWhitelistContextHandler(const std::initializer_list
}
QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QObject*) {};
QmlContextObjectCallback OffscreenQmlSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {};
struct TextureSet {
// The number of surfaces with this size
@ -590,6 +598,7 @@ void OffscreenQmlSurface::create() {
_qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow()));
_qmlContext->setContextProperty("eventBridge", this);
_qmlContext->setContextProperty("webEntity", this);
_qmlContext->setContextProperty("QmlSurface", this);
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
@ -688,20 +697,14 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) {
_qmlContext->setBaseUrl(baseUrl);
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& 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);
QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, bool forceNewContext) {
// Get any whitelist functionality
QList<QmlContextCallback> callbacks = getQmlWhitelist()->getCallbacksForUrl(qmlSource);
// If we have whitelisted content, we must load a new context
createNewContext |= !callbacks.empty();
forceNewContext |= !callbacks.empty();
QQmlContext* targetContext = _qmlContext;
if (_rootItem && createNewContext) {
if (_rootItem && forceNewContext) {
targetContext = new QQmlContext(targetContext);
}
@ -709,6 +712,15 @@ void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, con
callback(targetContext);
}
return targetContext;
}
void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& 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);
// FIXME eliminate loading of relative file paths for QML
QUrl finalQmlSource = qmlSource;
@ -716,17 +728,36 @@ void OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, con
finalQmlSource = _qmlContext->resolvedUrl(qmlSource);
}
auto targetContext = contextForUrl(finalQmlSource);
auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback);
finishQmlLoad(qmlComponent, targetContext, nullptr, onQmlLoadedCallback);
});
return;
}
finishQmlLoad(qmlComponent, targetContext, onQmlLoadedCallback);
finishQmlLoad(qmlComponent, targetContext, nullptr, onQmlLoadedCallback);
}
void OffscreenQmlSurface::createContentFromQml(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) {
auto targetContext = contextForUrl(qmlSource);
auto onQmlLoadedCallback = [=](QQmlContext*, QObject* newItem) {
QJSValue(callback).call(QJSValueList() << _qmlContext->engine()->newQObject(newItem));
};
auto qmlComponent = new QQmlComponent(_qmlContext->engine(), qmlSource, QQmlComponent::PreferSynchronous);
if (qmlComponent->isLoading()) {
connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) {
finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
});
return;
}
finishQmlLoad(qmlComponent, targetContext, parent, onQmlLoadedCallback);
}
void OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback) {
load(qmlSource, true, onQmlLoadedCallback);
}
@ -743,7 +774,8 @@ void OffscreenQmlSurface::clearCache() {
_qmlContext->engine()->clearComponentCache();
}
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QmlContextObjectCallback& callback) {
void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) {
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
if (qmlComponent->isError()) {
for (const auto& error : qmlComponent->errors()) {
@ -765,6 +797,22 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
return;
}
if (!newObject) {
if (!_rootItem) {
qFatal("Could not load object as root item");
return;
}
qCWarning(uiLogging) << "Unable to load QML item";
return;
}
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
}
qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership);
// All quick items should be focusable
@ -775,35 +823,26 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
newItem->setFlag(QQuickItem::ItemIsFocusScope, true);
}
// Make sure we will call callback for this codepath
// Call this before qmlComponent->completeCreate() otherwise ghost window appears
if (newItem && _rootItem) {
callback(qmlContext, newObject);
}
// If we already have a root, just set a couple of flags and the ancestry
if (_rootItem) {
callback(qmlContext, newItem);
QObject* eventBridge = qmlContext->contextProperty("eventBridge").value<QObject*>();
if (qmlContext != _qmlContext && eventBridge && eventBridge != this) {
// FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper
// Find a way to flag older scripts using this mechanism and wanr that this is deprecated
qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext));
if (!parent) {
parent = _rootItem;
}
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(parent);
newItem->setParentItem(parent);
}
qmlComponent->completeCreate();
qmlComponent->deleteLater();
// If we already have a root, just set a couple of flags and the ancestry
if (newItem && _rootItem) {
// Allow child windows to be destroyed from JS
QQmlEngine::setObjectOwnership(newObject, QQmlEngine::JavaScriptOwnership);
newObject->setParent(_rootItem);
if (newItem) {
newItem->setParentItem(_rootItem);
}
return;
}
if (!newItem) {
qFatal("Could not load object as root item");
if (_rootItem) {
return;
}
@ -815,7 +854,7 @@ void OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext
_rootItem->setSize(_quickWindow->renderTargetSize());
// Call this callback after rootitem is set, otherwise VrMenu wont work
callback(qmlContext, newObject);
callback(qmlContext, newItem);
}
void OffscreenQmlSurface::updateQuick() {

View file

@ -30,13 +30,14 @@ class QQmlContext;
class QQmlComponent;
class QQuickWindow;
class QQuickItem;
class QJSValue;
// GPU resources are typically buffered for one copy being used by the renderer,
// one copy in flight, and one copy being used by the receiver
#define GPU_RESOURCE_BUFFER_SIZE 3
using QmlContextCallback = std::function<void(QQmlContext*)>;
using QmlContextObjectCallback = std::function<void(QQmlContext*, QObject*)>;
using QmlContextObjectCallback = std::function<void(QQmlContext*, QQuickItem*)>;
class OffscreenQmlSurface : public QObject {
Q_OBJECT
@ -57,6 +58,8 @@ public:
void resize(const QSize& size, bool forceResize = false);
QSize size() const;
Q_INVOKABLE void createContentFromQml(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback);
Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& onQmlLoadedCallback = DEFAULT_CONTEXT_CALLBACK);
@ -123,9 +126,10 @@ protected:
void setFocusText(bool newFocusText);
private:
QQmlContext* contextForUrl(const QUrl& url, bool forceNewContext = false);
static QOpenGLContext* getSharedContext();
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, const QmlContextObjectCallback& callbacks);
void finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callbacks);
QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject);
void setupFbo();
bool allowNewFrame(uint8_t fps);

View file

@ -1,7 +1,7 @@
print("Launching web window");
qmlWindow = new OverlayWindow({
title: 'Test Qml',
source: "https://s3.amazonaws.com/DreamingContent/qml/content.qml",
source: "qrc:///qml/OverlayWindowTest.qml",
height: 240,
width: 320,
toolWindow: false,