From 0511f87bad977cc8ed26e5db0be729a150136964 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 5 Sep 2019 11:43:43 -0700 Subject: [PATCH] BUGZ-1398: Tablet access to local HTML https://youtu.be/7EWQOeQf32U --- libraries/qml/src/qml/OffscreenSurface.cpp | 4 ++++ libraries/qml/src/qml/OffscreenSurface.h | 1 + libraries/script-engine/src/ScriptEngine.cpp | 10 ++++++++ .../shared/src/shared/LocalFileAccessGate.cpp | 21 +++++++++++++++++ .../shared/src/shared/LocalFileAccessGate.h | 21 +++++++++++++++++ libraries/ui/src/ui/OffscreenQmlSurface.cpp | 21 +++++++++++++++++ libraries/ui/src/ui/OffscreenQmlSurface.h | 5 ++-- .../ui/src/ui/TabletScriptingInterface.cpp | 23 +++++++++++++++++-- .../ui/src/ui/TabletScriptingInterface.h | 7 ++++++ 9 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 libraries/shared/src/shared/LocalFileAccessGate.cpp create mode 100644 libraries/shared/src/shared/LocalFileAccessGate.h diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 117ff61c6e..a7290a4d7d 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -291,6 +291,10 @@ void OffscreenSurface::setMaxFps(uint8_t maxFps) { } void OffscreenSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { + loadFromQml(qmlSource, parent, callback); +} + +void OffscreenSurface::loadFromQml(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); }); diff --git a/libraries/qml/src/qml/OffscreenSurface.h b/libraries/qml/src/qml/OffscreenSurface.h index 528baf76be..8209398e05 100644 --- a/libraries/qml/src/qml/OffscreenSurface.h +++ b/libraries/qml/src/qml/OffscreenSurface.h @@ -111,6 +111,7 @@ signals: void rootItemCreated(QQuickItem* rootContext); protected: + virtual void loadFromQml(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); bool eventFilter(QObject* originalDestination, QEvent* event) override; bool filterEnabled(QObject* originalDestination, QEvent* event) const; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e679e9f6c5..44a9d89547 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -38,6 +38,7 @@ #include +#include #include #include #include @@ -1123,6 +1124,12 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi } void ScriptEngine::run() { + if (QThread::currentThread() != qApp->thread() && _context == Context::CLIENT_SCRIPT) { + // Flag that we're allowed to access local HTML files on UI created from C++ calls on this thread + // (because we're a client script) + hifi::scripting::setLocalAccessSafeThread(true); + } + auto filenameParts = _fileNameString.split("/"); auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; PROFILE_SET_THREAD_NAME("Script: " + name); @@ -1300,6 +1307,9 @@ void ScriptEngine::run() { emit finished(_fileNameString, qSharedPointerCast(sharedFromThis())); + // Don't leave our local-file-access flag laying around, reset it to false when the scriptengine + // thread is finished + hifi::scripting::setLocalAccessSafeThread(false); _isRunning = false; emit runningStateChanged(); emit doneRunning(); diff --git a/libraries/shared/src/shared/LocalFileAccessGate.cpp b/libraries/shared/src/shared/LocalFileAccessGate.cpp new file mode 100644 index 0000000000..4b57894251 --- /dev/null +++ b/libraries/shared/src/shared/LocalFileAccessGate.cpp @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis on 2019/09/05 +// Copyright 2013-2019 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 +// + +#include "LocalFileAccessGate.h" + +#include + +static QThreadStorage localAccessSafe; + +void hifi::scripting::setLocalAccessSafeThread(bool safe) { + localAccessSafe.setLocalData(safe); +} + +bool hifi::scripting::isLocalAccessSafeThread() { + return localAccessSafe.hasLocalData() && localAccessSafe.localData(); +} diff --git a/libraries/shared/src/shared/LocalFileAccessGate.h b/libraries/shared/src/shared/LocalFileAccessGate.h new file mode 100644 index 0000000000..7fc5563524 --- /dev/null +++ b/libraries/shared/src/shared/LocalFileAccessGate.h @@ -0,0 +1,21 @@ +// +// Created by Bradley Austin Davis on 2019/09/05 +// Copyright 2013-2019 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 +// + +#pragma once +#ifndef hifi_LocalFileAccessGate_h +#define hifi_LocalFileAccessGate_h + +namespace hifi { namespace scripting { + +void setLocalAccessSafeThread(bool safe = true); + +bool isLocalAccessSafeThread(); + +}} // namespace hifi::scripting + +#endif diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 35fb92a086..8beca598a2 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include @@ -814,4 +815,24 @@ void OffscreenQmlSurface::forceQmlAudioOutputDeviceUpdate() { #endif } +void OffscreenQmlSurface::loadFromQml(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { + auto objectCallback = [callback](QQmlContext* context, QQuickItem* newItem) { + QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); + }; + + if (hifi::scripting::isLocalAccessSafeThread()) { + // If this is a + auto contextCallback = [callback](QQmlContext* context) { + ContextAwareProfile::restrictContext(context, false); +#if !defined(Q_OS_ANDROID) + FileTypeProfile::registerWithContext(context); + HFWebEngineProfile::registerWithContext(context); +#endif + }; + loadInternal(qmlSource, true, parent, objectCallback, contextCallback); + } else { + loadInternal(qmlSource, false, parent, objectCallback); + } +} + #include "OffscreenQmlSurface.moc" diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 8bb22a8c79..76533a49cb 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -37,8 +37,8 @@ public: Q_INVOKABLE void lowerKeyboard(); PointerEvent::EventType choosePointerEventType(QEvent::Type type); Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y); - - + + signals: void focusObjectChanged(QObject* newFocus); void focusTextChanged(bool focusText); @@ -61,6 +61,7 @@ public slots: void sendToQml(const QVariant& message); protected: + void loadFromQml(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) override; void clearFocusItem(); void setFocusText(bool newFocusText); void initializeEngine(QQmlEngine* engine) override; diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index a8f319286b..c54f63690d 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -635,13 +636,24 @@ void TabletProxy::returnToPreviousApp() { qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null"; } } - + void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { + // Capture whether the current script thread is allowed to load local HTML content, + // pass the information along to the real function + bool localSafeContext = hifi::scripting::isLocalAccessSafeThread(); if (QThread::currentThread() != thread()) { - QMetaObject::invokeMethod(this, "loadQMLSource", Q_ARG(QVariant, path), Q_ARG(bool, resizable)); + QMetaObject::invokeMethod(this, "loadQMLSourceImpl", Q_ARG(QVariant, path), Q_ARG(bool, resizable), Q_ARG(bool, localSafeContext)); return; } + loadQMLSourceImpl(path, resizable, localSafeContext); +} +void TabletProxy::loadQMLSourceImpl(const QVariant& path, bool resizable, bool localSafeContext) { + if (QThread::currentThread() != thread()) { + qCWarning(uiLogging) << __FUNCTION__ << "may not be called directly by scripts"; + return; + + } QObject* root = nullptr; if (!_toolbarMode && _qmlTabletRoot) { root = _qmlTabletRoot; @@ -650,7 +662,14 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { } if (root) { + // BUGZ-1398: tablet access to local HTML files from client scripts + // Here we TEMPORARILY mark the main thread as allowed to load local file content, + // because the thread that originally made the call is so marked. + if (localSafeContext) { + hifi::scripting::setLocalAccessSafeThread(true); + } QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); + hifi::scripting::setLocalAccessSafeThread(false); _state = State::QML; _currentPathLoaded = path; QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index afd77a15f6..ba02ba25b0 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -291,6 +291,13 @@ public: * to have it not resizable. */ Q_INVOKABLE void loadQMLSource(const QVariant& path, bool resizable = false); + + /**jsdoc + * Internal function, do not call from scripts + * @function TabletProxy#loadQMLSourceImpl + */ + Q_INVOKABLE void loadQMLSourceImpl(const QVariant& path, bool resizable, bool localSafeContext); + // FIXME: This currently relies on a script initializing the tablet (hence the bool denoting success); // it should be initialized internally so it cannot fail