From 2662dc9b4e5ba665556ed60d791b3803ff5eb18f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:03:48 -0700 Subject: [PATCH 1/9] Suppress tracing log spam when tracing is not active --- libraries/shared/src/Trace.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/shared/src/Trace.h b/libraries/shared/src/Trace.h index 93e2c6c4c2..1e1326968f 100644 --- a/libraries/shared/src/Trace.h +++ b/libraries/shared/src/Trace.h @@ -102,6 +102,9 @@ private: }; inline void traceEvent(const QLoggingCategory& category, const QString& name, EventType type, const QString& id = "", const QVariantMap& args = {}, const QVariantMap& extra = {}) { + if (!DependencyManager::isSet()) { + return; + } const auto& tracer = DependencyManager::get(); if (tracer) { tracer->traceEvent(category, name, type, id, args, extra); From 4db83230ce94b966efdfe07553d368be68a6e893 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:09:31 -0700 Subject: [PATCH 2/9] Fix bad virtual cast on qml surface destruction --- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 23 +++++++++++++-------- libraries/ui/src/ui/OffscreenQmlSurface.h | 4 +++- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 43b573a169..b3c1c486e9 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -223,6 +223,17 @@ void AudioHandler::run() { qDebug() << "QML Audio changed to " << _newTargetDevice; } +OffscreenQmlSurface::~OffscreenQmlSurface() { + clearFocusItem(); +} + +void OffscreenQmlSurface::clearFocusItem() { + if (_currentFocusItem) { + disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); + } + _currentFocusItem = nullptr; +} + void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { Parent::initializeEngine(engine); new QQmlFileSelector(engine); @@ -545,17 +556,15 @@ bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QT } void OffscreenQmlSurface::focusDestroyed(QObject* obj) { - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, &OffscreenQmlSurface::focusDestroyed); - } - _currentFocusItem = nullptr; + clearFocusItem(); } void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { + clearFocusItem(); + QQuickItem* item = static_cast(object); if (!item) { setFocusText(false); - _currentFocusItem = nullptr; return; } @@ -563,10 +572,6 @@ void OffscreenQmlSurface::onFocusObjectChanged(QObject* object) { qApp->sendEvent(object, &query); setFocusText(query.value(Qt::ImEnabled).toBool()); - if (_currentFocusItem) { - disconnect(_currentFocusItem, &QObject::destroyed, this, 0); - } - // Raise and lower keyboard for QML text fields. // HTML text fields are handled in emitWebEvent() methods - testing READ_ONLY_PROPERTY prevents action for HTML files. const char* READ_ONLY_PROPERTY = "readOnly"; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 9fa86f12a3..b95a8f117d 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -22,7 +22,8 @@ class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { Q_OBJECT Q_PROPERTY(bool focusText READ isFocusText NOTIFY focusTextChanged) public: - + ~OffscreenQmlSurface(); + static void addWhitelistContextHandler(const std::initializer_list& urls, const QmlContextCallback& callback); static void addWhitelistContextHandler(const QUrl& url, const QmlContextCallback& callback) { addWhitelistContextHandler({ { url } }, callback); }; @@ -58,6 +59,7 @@ public slots: void sendToQml(const QVariant& message); protected: + void clearFocusItem(); void setFocusText(bool newFocusText); void initializeEngine(QQmlEngine* engine) override; void onRootContextCreated(QQmlContext* qmlContext) override; From 6640313256e0fadc6401628f6e0316518a01119a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:10:22 -0700 Subject: [PATCH 3/9] Fix memory leak in QML surfaces --- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index b3c1c486e9..0d8e22cebb 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -115,6 +115,7 @@ private: class UrlHandler : public QObject { Q_OBJECT public: + UrlHandler(QObject* parent = nullptr) : QObject(parent) {} Q_INVOKABLE bool canHandleUrl(const QString& url) { static auto handler = dynamic_cast(qApp); return handler && handler->canAcceptURL(url); @@ -257,7 +258,7 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { auto rootContext = engine->rootContext(); rootContext->setContextProperty("GL", ::getGLContextData()); - rootContext->setContextProperty("urlHandler", new UrlHandler()); + rootContext->setContextProperty("urlHandler", new UrlHandler(rootContext)); rootContext->setContextProperty("resourceDirectoryUrl", QUrl::fromLocalFile(PathUtils::resourcesPath())); rootContext->setContextProperty("ApplicationInterface", qApp); auto javaScriptToInject = getEventBridgeJavascript(); From 1b612d373f845ea4dab1698fb36b12d5814d88ff Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:11:11 -0700 Subject: [PATCH 4/9] Explicitly delete QML context before releasing engine --- libraries/qml/src/qml/impl/SharedObject.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 9253c41b39..7bcb216ba8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -97,12 +97,13 @@ SharedObject::~SharedObject() { } // _rootItem is parented to the quickWindow, so needs no explicit destruction - //if (_rootItem) { - // delete _rootItem; - // _rootItem = nullptr; - //} - releaseEngine(_qmlContext->engine()); + if (_qmlContext) { + auto engine = _qmlContext->engine(); + delete _qmlContext; + _qmlContext = nullptr; + releaseEngine(engine); + } } void SharedObject::create(OffscreenSurface* surface) { @@ -210,9 +211,9 @@ QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { if (!globalEngine) { Q_ASSERT(0 == globalEngineRefCount); globalEngine = new QQmlEngine(); - surface->initializeQmlEngine(result); - ++globalEngineRefCount; + surface->initializeEngine(result); } + ++globalEngineRefCount; result = globalEngine; #else result = new QQmlEngine(); From e892694bf5bdcf56e5892a71fe7fb2492f7d42ec Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:13:35 -0700 Subject: [PATCH 5/9] Don't modify URL on web entity on QML shutdown, remove tablet related code from web entity --- .../src/RenderableWebEntityItem.cpp | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f333e805ce..7ad74d1eee 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -308,12 +308,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { - _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { - if (item && item->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data()); - } - }); + _webSurface->load(_lastSourceUrl); } _fadeStartTime = usecTimestampNow(); _webSurface->resume(); @@ -330,16 +325,6 @@ void WebEntityRenderer::destroyWebSurface() { if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); - // Explicitly set the web URL to an empty string, in an effort to get a - // faster shutdown of any chromium processes interacting with audio - if (rootItem && _contentType == ContentType::HtmlContent) { - rootItem->setProperty(URL_PROPERTY, ""); - } - - if (rootItem && rootItem->objectName() == "tabletRoot") { - auto tabletScriptingInterface = DependencyManager::get(); - tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", nullptr); - } // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. From f2172d84a00d8add357029821c45f4d74d15d412 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 08:35:20 -0700 Subject: [PATCH 6/9] Add stress test for QML web content --- tests/qml/qml/controls/WebEntityView.qml | 32 ++++ tests/qml/src/main.cpp | 198 ++++++++++++++++++----- 2 files changed, 187 insertions(+), 43 deletions(-) create mode 100644 tests/qml/qml/controls/WebEntityView.qml diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml new file mode 100644 index 0000000000..a6c38f4abd --- /dev/null +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -0,0 +1,32 @@ +// +// WebEntityView.qml +// +// Created by Kunal Gosar on 16 March 2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.5 +import QtWebEngine 1.5 + +WebEngineView { + id: webViewCore + objectName: "webEngineView" + width: parent !== null ? parent.width : undefined + height: parent !== null ? parent.height : undefined + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + //disable popup + onContextMenuRequested: { + request.accepted = true; + } + + onNewViewRequested: { + newViewRequestedCallback(request) + } +} diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index 022f7290f4..c23a958da7 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -28,52 +28,82 @@ #include #include #include - +#include #include #include +#include + #include -#include #include #include #include #include #include #include +#include +#include + +#pragma optimize("", off) + +namespace gl { + extern void initModuleGl(); +} -class OffscreenQmlSurface : public hifi::qml::OffscreenSurface { +QUrl getTestResource(const QString& relativePath) { + static QString dir; + if (dir.isEmpty()) { + QDir path(__FILE__); + path.cdUp(); + dir = path.cleanPath(path.absoluteFilePath("../")) + "/"; + qDebug() << "Resources Path: " << dir; + } + return QUrl::fromLocalFile(dir + relativePath); +} + +#define DIVISIONS_X 5 +#define DIVISIONS_Y 5 + +using QmlPtr = QSharedPointer; +using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; + +struct QmlInfo { + QmlPtr surface; + GLuint texture{ 0 }; + uint64_t lifetime{ 0 }; }; class TestWindow : public QWindow { - public: TestWindow(); - private: - using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence; QOpenGLContext _glContext; OffscreenGLCanvas _sharedContext; - OffscreenQmlSurface _offscreenQml; + std::array, DIVISIONS_X> _surfaces; + QOpenGLFunctions_4_5_Core _glf; - uint32_t _currentTexture{ 0 }; - GLsync _readFence{ 0 }; std::function _discardLamdba; QSize _size; + size_t _surfaceCount{ 0 }; GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; void initGl(); + void updateSurfaces(); + void buildSurface(QmlInfo& qmlInfo); + void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); void resizeEvent(QResizeEvent* ev) override; }; TestWindow::TestWindow() { - setSurfaceType(QSurface::OpenGLSurface); + Setting::init(); + setSurfaceType(QSurface::OpenGLSurface); QSurfaceFormat format; format.setDepthBufferSize(24); format.setStencilBufferSize(8); @@ -85,11 +115,12 @@ TestWindow::TestWindow() { show(); + resize(QSize(800, 600)); auto timer = new QTimer(this); timer->setTimerType(Qt::PreciseTimer); - timer->setInterval(5); + timer->setInterval(30); connect(timer, &QTimer::timeout, [&] { draw(); }); timer->start(); @@ -97,7 +128,6 @@ TestWindow::TestWindow() { timer->stop(); _aboutToQuit = true; }); - } void TestWindow::initGl() { @@ -105,6 +135,7 @@ void TestWindow::initGl() { if (!_glContext.create() || !_glContext.makeCurrent(this)) { qFatal("Unable to intialize Window GL context"); } + gl::initModuleGl(); _glf.initializeOpenGLFunctions(); _glf.glCreateFramebuffers(1, &_fbo); @@ -113,15 +144,104 @@ void TestWindow::initGl() { qFatal("Unable to intialize Shared GL context"); } hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext()); - _discardLamdba = _offscreenQml.getDiscardLambda(); - _offscreenQml.resize({ 640, 480 }); - _offscreenQml.load(QUrl::fromLocalFile("C:/Users/bdavi/Git/hifi/tests/qml/qml/main.qml")); + _discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda(); } void TestWindow::resizeWindow(const QSize& size) { _size = size; } +static const int DEFAULT_MAX_FPS = 10; +static const int YOUTUBE_MAX_FPS = 30; +static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; +static const char* URL_PROPERTY{ "url" }; + +QString getSourceUrl() { + static const std::vector SOURCE_URLS{ + "https://www.reddit.com/wiki/random", + "https://en.wikipedia.org/wiki/Wikipedia:Random", + "https://slashdot.org/", + //"https://www.youtube.com/watch?v=gDXwhHm4GhM", + //"https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + auto index = rand() % SOURCE_URLS.size(); + return SOURCE_URLS[index]; +} + + + +void TestWindow::buildSurface(QmlInfo& qmlInfo) { + ++_surfaceCount; + auto lifetimeSecs = (uint32_t)(2.0f + (randFloat() * 10.0f)); + auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); + qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); + qmlInfo.texture = 0; + auto& surface = qmlInfo.surface; + surface.reset(new hifi::qml::OffscreenSurface()); + surface->setMaxFps(DEFAULT_MAX_FPS); + surface->resize(_qmlSize); + surface->setMaxFps(DEFAULT_MAX_FPS); + hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl()); + }; + surface->load(getTestResource(CONTROL_URL), callback); + surface->resume(); +} + +void TestWindow::destroySurface(QmlInfo& qmlInfo) { + auto& surface = qmlInfo.surface; + QQuickItem* rootItem = surface->getRootItem(); + if (rootItem) { + QObject* obj = rootItem->findChild("webEngineView"); + if (!obj && rootItem->objectName() == "webEngineView") { + obj = rootItem; + } + if (obj) { + // stop loading + QMetaObject::invokeMethod(obj, "stop"); + } + } + surface->pause(); + surface.reset(); +} + +void TestWindow::updateSurfaces() { + auto now = usecTimestampNow(); + // Fetch any new textures + for (size_t x = 0; x < DIVISIONS_X; ++x) { + for (size_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface) { + if (randFloat() > 0.99f) { + buildSurface(qmlInfo); + } else { + continue; + } + } + + if (now > qmlInfo.lifetime) { + destroySurface(qmlInfo); + continue; + } + + auto& surface = qmlInfo.surface; + auto& currentTexture = qmlInfo.texture; + + TextureAndFence newTextureAndFence; + if (surface->fetchTexture(newTextureAndFence)) { + if (currentTexture != 0) { + auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + _discardLamdba(currentTexture, readFence); + } + currentTexture = newTextureAndFence.first; + _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); + } + } + } +} + void TestWindow::draw() { if (_aboutToQuit) { return; @@ -140,36 +260,31 @@ void TestWindow::draw() { return; } + updateSurfaces(); + + auto size = this->geometry().size(); + auto incrementX = size.width() / DIVISIONS_X; + auto incrementY = size.height() / DIVISIONS_Y; + _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - TextureAndFence newTextureAndFence; - if (_offscreenQml.fetchTexture(newTextureAndFence)) { - if (_currentTexture) { - _discardLamdba(_currentTexture, _readFence); - _readFence = 0; + for (uint32_t x = 0; x < DIVISIONS_X; ++x) { + for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { + auto& qmlInfo = _surfaces[x][y]; + if (!qmlInfo.surface || !qmlInfo.texture) { + continue; + } + _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, qmlInfo.texture, 0); + _glf.glBlitNamedFramebuffer(_fbo, 0, + // src coordinates + 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, + // dst coordinates + incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1), + // blit mask and filter + GL_COLOR_BUFFER_BIT, GL_NEAREST); } - - _currentTexture = newTextureAndFence.first; - _glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED); - _glf.glNamedFramebufferTexture(_fbo, GL_COLOR_ATTACHMENT0, _currentTexture, 0); } - - auto diff = _size - _qmlSize; - diff /= 2; - auto qmlExtent = diff + _qmlSize; - - if (_currentTexture) { - _glf.glBlitNamedFramebuffer(_fbo, 0, - 0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1, - diff.width(), diff.height(), qmlExtent.width() - 1, qmlExtent.height() - 2, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - } - - if (_readFence) { - _glf.glDeleteSync(_readFence); - } - _readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); _glf.glFlush(); _glContext.swapBuffers(this); @@ -180,11 +295,8 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { } int main(int argc, char** argv) { - setupHifiApplication("QML Test"); - QGuiApplication app(argc, argv); TestWindow window; app.exec(); return 0; } - From 5f8149fd6873ca6ad1163927826283f1809a10c8 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 13:57:22 -0700 Subject: [PATCH 7/9] Remove debugging pragma --- tests/qml/src/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index c23a958da7..cd5a567e95 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -44,8 +44,6 @@ #include #include -#pragma optimize("", off) - namespace gl { extern void initModuleGl(); } From 575520fe3063f3d6deac5e8266b3e886ac25d88a Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 15:08:10 -0700 Subject: [PATCH 8/9] Ensure we actually delete the QML scenegraph! --- libraries/qml/src/qml/impl/SharedObject.cpp | 7 +- tests/qml/qml/controls/WebEntityView.qml | 15 ++++ tests/qml/src/main.cpp | 84 ++++++++++++++------- 3 files changed, 78 insertions(+), 28 deletions(-) diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index 7bcb216ba8..2fde057ca8 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -90,14 +90,17 @@ SharedObject::~SharedObject() { _renderControl = nullptr; } + if (_rootItem) { + delete _rootItem; + _rootItem = nullptr; + } + if (_quickWindow) { _quickWindow->destroy(); delete _quickWindow; _quickWindow = nullptr; } - // _rootItem is parented to the quickWindow, so needs no explicit destruction - if (_qmlContext) { auto engine = _qmlContext->engine(); delete _qmlContext; diff --git a/tests/qml/qml/controls/WebEntityView.qml b/tests/qml/qml/controls/WebEntityView.qml index a6c38f4abd..5bd29ef457 100644 --- a/tests/qml/qml/controls/WebEntityView.qml +++ b/tests/qml/qml/controls/WebEntityView.qml @@ -10,6 +10,21 @@ import QtQuick 2.5 import QtWebEngine 1.5 +import Hifi 1.0 + +/* +TestItem { + Rectangle { + anchors.fill: parent + anchors.margins: 10 + color: "blue" + property string url: "" + ColorAnimation on color { + loops: Animation.Infinite; from: "blue"; to: "yellow"; duration: 1000 + } + } +} +*/ WebEngineView { id: webViewCore diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index cd5a567e95..ec4012898f 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -48,6 +48,17 @@ namespace gl { extern void initModuleGl(); } +class QTestItem : public QQuickItem { + Q_OBJECT +public: + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { + qDebug() << __FUNCTION__; + } + + ~QTestItem() { + qDebug() << __FUNCTION__; + } +}; QUrl getTestResource(const QString& relativePath) { static QString dir; @@ -89,9 +100,10 @@ private: GLuint _fbo{ 0 }; const QSize _qmlSize{ 640, 480 }; bool _aboutToQuit{ false }; + uint64_t _createStopTime; void initGl(); void updateSurfaces(); - void buildSurface(QmlInfo& qmlInfo); + void buildSurface(QmlInfo& qmlInfo, bool allowVideo); void destroySurface(QmlInfo& qmlInfo); void resizeWindow(const QSize& size); void draw(); @@ -111,8 +123,10 @@ TestWindow::TestWindow() { QSurfaceFormat::setDefaultFormat(format); setFormat(format); - show(); + qmlRegisterType("Hifi", 1, 0, "TestItem"); + show(); + _createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND); resize(QSize(800, 600)); @@ -167,40 +181,56 @@ QString getSourceUrl() { return SOURCE_URLS[index]; } +#define CACHE_WEBVIEWS 0 +#if CACHE_WEBVIEWS +static std::list _cache; +#endif -void TestWindow::buildSurface(QmlInfo& qmlInfo) { +hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl()); +}; + +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool allowVideo) { ++_surfaceCount; - auto lifetimeSecs = (uint32_t)(2.0f + (randFloat() * 10.0f)); + auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); qmlInfo.texture = 0; - auto& surface = qmlInfo.surface; - surface.reset(new hifi::qml::OffscreenSurface()); - surface->setMaxFps(DEFAULT_MAX_FPS); - surface->resize(_qmlSize); - surface->setMaxFps(DEFAULT_MAX_FPS); - hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl()); - }; - surface->load(getTestResource(CONTROL_URL), callback); - surface->resume(); +#if CACHE_WEBVIEWS + if (_cache.empty()) { + _cache.emplace_back(new hifi::qml::OffscreenSurface()); + auto& surface = _cache.back(); + surface->load(getTestResource(CONTROL_URL)); + surface->setMaxFps(DEFAULT_MAX_FPS); + } + qmlInfo.surface = _cache.front(); + _cache.pop_front(); +#else + qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); + qmlInfo.surface->load(getTestResource(CONTROL_URL)); + qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); +#endif + + qmlInfo.surface->resize(_qmlSize); + auto url = allowVideo ? "https://www.youtube.com/watch?v=gDXwhHm4GhM" : getSourceUrl(); + qmlInfo.surface->getRootItem()->setProperty(URL_PROPERTY, url); + qmlInfo.surface->resume(); } + void TestWindow::destroySurface(QmlInfo& qmlInfo) { auto& surface = qmlInfo.surface; - QQuickItem* rootItem = surface->getRootItem(); - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (!obj && rootItem->objectName() == "webEngineView") { - obj = rootItem; - } - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + auto webView = surface->getRootItem(); + if (webView) { + // stop loading + QMetaObject::invokeMethod(webView, "stop"); + webView->setProperty(URL_PROPERTY, "about:blank"); } surface->pause(); +#if CACHE_WEBVIEWS + _cache.push_back(surface); +#endif surface.reset(); } @@ -211,8 +241,8 @@ void TestWindow::updateSurfaces() { for (size_t y = 0; y < DIVISIONS_Y; ++y) { auto& qmlInfo = _surfaces[x][y]; if (!qmlInfo.surface) { - if (randFloat() > 0.99f) { - buildSurface(qmlInfo); + if (now < _createStopTime && randFloat() > 0.99f) { + buildSurface(qmlInfo, x == 0 && y == 0); } else { continue; } @@ -298,3 +328,5 @@ int main(int argc, char** argv) { app.exec(); return 0; } + +#include "main.moc" \ No newline at end of file From 8e42bb8c877e693cdf29bdcb2e206316a2bc8a85 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Fri, 27 Apr 2018 16:04:07 -0700 Subject: [PATCH 9/9] Restore the stop functionality for a browser view when it's being destroyed --- .../qml/controls/FlickableWebViewCore.qml | 5 ++ interface/resources/qml/controls/WebView.qml | 4 ++ .../src/RenderableWebEntityItem.cpp | 11 ++- tests/qml/src/main.cpp | 68 +++++-------------- 4 files changed, 32 insertions(+), 56 deletions(-) diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 8e7db44b7d..943f15e1de 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -21,6 +21,7 @@ Item { signal newViewRequestedCallback(var request) signal loadingChangedCallback(var loadRequest) + width: parent.width property bool interactive: false @@ -29,6 +30,10 @@ Item { id: hifi } + function stop() { + webViewCore.stop(); + } + function unfocus() { webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) { console.log('unfocus completed: ', result); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 931c64e1ef..71bf69fdc8 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -21,6 +21,10 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive + function stop() { + webroot.stop(); + } + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. /* diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 7ad74d1eee..693e3d0cf4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -318,8 +318,10 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { void WebEntityRenderer::destroyWebSurface() { QSharedPointer webSurface; + ContentType contentType{ ContentType::NoContent }; withWriteLock([&] { webSurface.swap(_webSurface); + std::swap(contentType, _contentType); }); if (webSurface) { @@ -328,12 +330,9 @@ void WebEntityRenderer::destroyWebSurface() { // Fix for crash in QtWebEngineCore when rapidly switching domains // Call stop on the QWebEngineView before destroying OffscreenQMLSurface. - if (rootItem) { - QObject* obj = rootItem->findChild("webEngineView"); - if (obj) { - // stop loading - QMetaObject::invokeMethod(obj, "stop"); - } + if (rootItem && contentType == ContentType::HtmlContent) { + // stop loading + QMetaObject::invokeMethod(rootItem, "stop"); } webSurface->pause(); diff --git a/tests/qml/src/main.cpp b/tests/qml/src/main.cpp index ec4012898f..349ac55d88 100644 --- a/tests/qml/src/main.cpp +++ b/tests/qml/src/main.cpp @@ -45,19 +45,15 @@ #include namespace gl { - extern void initModuleGl(); +extern void initModuleGl(); } class QTestItem : public QQuickItem { Q_OBJECT public: - QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { - qDebug() << __FUNCTION__; - } + QTestItem(QQuickItem* parent = nullptr) : QQuickItem(parent) { qDebug() << __FUNCTION__; } - ~QTestItem() { - qDebug() << __FUNCTION__; - } + ~QTestItem() { qDebug() << __FUNCTION__; } }; QUrl getTestResource(const QString& relativePath) { @@ -71,7 +67,6 @@ QUrl getTestResource(const QString& relativePath) { return QUrl::fromLocalFile(dir + relativePath); } - #define DIVISIONS_X 5 #define DIVISIONS_Y 5 @@ -164,61 +159,41 @@ void TestWindow::resizeWindow(const QSize& size) { } static const int DEFAULT_MAX_FPS = 10; -static const int YOUTUBE_MAX_FPS = 30; static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" }; static const char* URL_PROPERTY{ "url" }; -QString getSourceUrl() { +QString getSourceUrl(bool video) { static const std::vector SOURCE_URLS{ "https://www.reddit.com/wiki/random", "https://en.wikipedia.org/wiki/Wikipedia:Random", "https://slashdot.org/", - //"https://www.youtube.com/watch?v=gDXwhHm4GhM", - //"https://www.youtube.com/watch?v=Ch_hoYPPeGc", }; - auto index = rand() % SOURCE_URLS.size(); - return SOURCE_URLS[index]; + static const std::vector VIDEO_SOURCE_URLS{ + "https://www.youtube.com/watch?v=gDXwhHm4GhM", + "https://www.youtube.com/watch?v=Ch_hoYPPeGc", + }; + + const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS; + auto index = rand() % sourceUrls.size(); + return sourceUrls[index]; } -#define CACHE_WEBVIEWS 0 - -#if CACHE_WEBVIEWS -static std::list _cache; -#endif - -hifi::qml::QmlContextObjectCallback callback = [](QQmlContext* context, QQuickItem* item) { - item->setProperty(URL_PROPERTY, getSourceUrl()); -}; - -void TestWindow::buildSurface(QmlInfo& qmlInfo, bool allowVideo) { +void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) { ++_surfaceCount; auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f)); auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs); qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow(); qmlInfo.texture = 0; -#if CACHE_WEBVIEWS - if (_cache.empty()) { - _cache.emplace_back(new hifi::qml::OffscreenSurface()); - auto& surface = _cache.back(); - surface->load(getTestResource(CONTROL_URL)); - surface->setMaxFps(DEFAULT_MAX_FPS); - } - qmlInfo.surface = _cache.front(); - _cache.pop_front(); -#else qmlInfo.surface.reset(new hifi::qml::OffscreenSurface()); - qmlInfo.surface->load(getTestResource(CONTROL_URL)); + qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) { + item->setProperty(URL_PROPERTY, getSourceUrl(video)); + }); qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS); -#endif - qmlInfo.surface->resize(_qmlSize); - auto url = allowVideo ? "https://www.youtube.com/watch?v=gDXwhHm4GhM" : getSourceUrl(); - qmlInfo.surface->getRootItem()->setProperty(URL_PROPERTY, url); qmlInfo.surface->resume(); } - void TestWindow::destroySurface(QmlInfo& qmlInfo) { auto& surface = qmlInfo.surface; auto webView = surface->getRootItem(); @@ -228,9 +203,6 @@ void TestWindow::destroySurface(QmlInfo& qmlInfo) { webView->setProperty(URL_PROPERTY, "about:blank"); } surface->pause(); -#if CACHE_WEBVIEWS - _cache.push_back(surface); -#endif surface.reset(); } @@ -296,7 +268,6 @@ void TestWindow::draw() { _glf.glViewport(0, 0, size.width(), size.height()); _glf.glClearColor(1, 0, 0, 1); _glf.glClear(GL_COLOR_BUFFER_BIT); - for (uint32_t x = 0; x < DIVISIONS_X; ++x) { for (uint32_t y = 0; y < DIVISIONS_Y; ++y) { auto& qmlInfo = _surfaces[x][y]; @@ -313,8 +284,6 @@ void TestWindow::draw() { GL_COLOR_BUFFER_BIT, GL_NEAREST); } } - _glf.glFlush(); - _glContext.swapBuffers(this); } @@ -325,8 +294,7 @@ void TestWindow::resizeEvent(QResizeEvent* ev) { int main(int argc, char** argv) { QGuiApplication app(argc, argv); TestWindow window; - app.exec(); - return 0; + return app.exec(); } -#include "main.moc" \ No newline at end of file +#include "main.moc"