diff options
Diffstat (limited to 'rockworkd/libpebble/jskit/jskitmanager.cpp')
| -rw-r--r-- | rockworkd/libpebble/jskit/jskitmanager.cpp | 240 |
1 files changed, 240 insertions, 0 deletions
diff --git a/rockworkd/libpebble/jskit/jskitmanager.cpp b/rockworkd/libpebble/jskit/jskitmanager.cpp new file mode 100644 index 0000000..04bf674 --- /dev/null +++ b/rockworkd/libpebble/jskit/jskitmanager.cpp @@ -0,0 +1,240 @@ +#include <QFile> +#include <QDir> +#include <QUrl> + +#include "jskitmanager.h" +#include "jskitpebble.h" + +JSKitManager::JSKitManager(Pebble *pebble, WatchConnection *connection, AppManager *apps, AppMsgManager *appmsg, QObject *parent) : + QObject(parent), + l(metaObject()->className()), + m_pebble(pebble), + m_connection(connection), + m_apps(apps), + m_appmsg(appmsg), + m_engine(0), + m_configurationUuid(0) +{ + connect(m_appmsg, &AppMsgManager::appStarted, this, &JSKitManager::handleAppStarted); + connect(m_appmsg, &AppMsgManager::appStopped, this, &JSKitManager::handleAppStopped); +} + +JSKitManager::~JSKitManager() +{ + if (m_engine) { + stopJsApp(); + } +} + +QJSEngine * JSKitManager::engine() +{ + return m_engine; +} + +bool JSKitManager::isJSKitAppRunning() const +{ + return m_engine != 0; +} + +QString JSKitManager::describeError(QJSValue error) +{ + return QString("%1:%2: %3") + .arg(error.property("fileName").toString()) + .arg(error.property("lineNumber").toInt()) + .arg(error.toString()); +} + +void JSKitManager::showConfiguration() +{ + if (m_engine) { + qCDebug(l) << "requesting configuration"; + m_jspebble->invokeCallbacks("showConfiguration"); + } else { + qCWarning(l) << "requested to show configuration, but JS engine is not running"; + } +} + +void JSKitManager::handleWebviewClosed(const QString &result) +{ + if (m_engine) { + QJSValue eventObj = m_engine->newObject(); + eventObj.setProperty("response", QUrl::fromPercentEncoding(result.toUtf8())); + + qCDebug(l) << "Sending" << eventObj.property("response").toString(); + m_jspebble->invokeCallbacks("webviewclosed", QJSValueList({eventObj})); + + loadJsFile(":/cacheLocalStorage.js"); + } else { + qCWarning(l) << "webview closed event, but JS engine is not running"; + } +} + +void JSKitManager::setConfigurationId(const QUuid &uuid) +{ + m_configurationUuid = uuid; +} + +AppInfo JSKitManager::currentApp() +{ + return m_curApp; +} + +void JSKitManager::handleAppStarted(const QUuid &uuid) +{ + AppInfo info = m_apps->info(uuid); + if (!info.uuid().isNull() && info.isJSKit()) { + qCDebug(l) << "Preparing to start JSKit app" << info.uuid() << info.shortName(); + + m_curApp = info; + startJsApp(); + } +} + +void JSKitManager::handleAppStopped(const QUuid &uuid) +{ + if (!m_curApp.uuid().isNull()) { + if (m_curApp.uuid() != uuid) { + qCWarning(l) << "Closed app with invalid UUID"; + } + + stopJsApp(); + m_curApp = AppInfo(); + qCDebug(l) << "App stopped" << uuid; + } +} + +void JSKitManager::handleAppMessage(const QUuid &uuid, const QVariantMap &msg) +{ + if (m_curApp.uuid() == uuid) { + qCDebug(l) << "handling app message" << uuid << msg; + + if (m_engine) { + QJSValue eventObj = m_engine->newObject(); + eventObj.setProperty("payload", m_engine->toScriptValue(msg)); + + m_jspebble->invokeCallbacks("appmessage", QJSValueList({eventObj})); + + loadJsFile(":/cacheLocalStorage.js"); + } + else { + qCDebug(l) << "but engine is stopped"; + } + } +} + +bool JSKitManager::loadJsFile(const QString &filename) +{ + Q_ASSERT(m_engine); + + QFile file(filename); + if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCWarning(l) << "Failed to load JS file:" << file.fileName(); + return false; + } + + qCDebug(l) << "evaluating js file" << file.fileName(); + + QJSValue result = m_engine->evaluate(QString::fromUtf8(file.readAll()), file.fileName()); + if (result.isError()) { + qCWarning(l) << "error while evaluating JS script:" << describeError(result); + return false; + } + + qCDebug(l) << "JS script evaluated"; + return true; +} + +void JSKitManager::startJsApp() +{ + if (m_engine) stopJsApp(); + + if (m_curApp.uuid().isNull()) { + qCWarning(l) << "Attempting to start JS app with invalid UUID"; + return; + } + + m_engine = new QJSEngine(this); + m_jspebble = new JSKitPebble(m_curApp, this, m_engine); + m_jsconsole = new JSKitConsole(m_engine); + m_jsstorage = new JSKitLocalStorage(m_engine, m_pebble->storagePath(), m_curApp.uuid()); + m_jsgeo = new JSKitGeolocation(m_engine); + m_jstimer = new JSKitTimer(m_engine); + m_jsperformance = new JSKitPerformance(m_engine); + + qCDebug(l) << "starting JS app" << m_curApp.shortName(); + + QJSValue globalObj = m_engine->globalObject(); + QJSValue jskitObj = m_engine->newObject(); + + jskitObj.setProperty("pebble", m_engine->newQObject(m_jspebble)); + jskitObj.setProperty("console", m_engine->newQObject(m_jsconsole)); + jskitObj.setProperty("localstorage", m_engine->newQObject(m_jsstorage)); + jskitObj.setProperty("geolocation", m_engine->newQObject(m_jsgeo)); + jskitObj.setProperty("timer", m_engine->newQObject(m_jstimer)); + jskitObj.setProperty("performance", m_engine->newQObject(m_jsperformance)); + globalObj.setProperty("_jskit", jskitObj); + + QJSValue navigatorObj = m_engine->newObject(); + navigatorObj.setProperty("language", m_engine->toScriptValue(QLocale().name())); + globalObj.setProperty("navigator", navigatorObj); + + // Set this.window = this + globalObj.setProperty("window", globalObj); + + // Shims for compatibility... + loadJsFile(":/jskitsetup.js"); + + // Polyfills... + loadJsFile(":/typedarray.js"); + + // Now the actual script + QString jsApp = m_curApp.file(AppInfo::FileTypeJsApp, HardwarePlatformUnknown); + QFile f(jsApp); + if (!f.open(QFile::ReadOnly)) { + qCWarning(l) << "Error opening" << jsApp; + return; + } + QJSValue ret = m_engine->evaluate(QString::fromUtf8(f.readAll())); + qCDebug(l) << "loaded script" << ret.toString(); + + // Setup the message callback + QUuid uuid = m_curApp.uuid(); + m_appmsg->setMessageHandler(uuid, [this, uuid](const QVariantMap &msg) { + QMetaObject::invokeMethod(this, "handleAppMessage", Qt::QueuedConnection, + Q_ARG(QUuid, uuid), + Q_ARG(QVariantMap, msg)); + + // Invoke the slot as a queued connection to give time for the ACK message + // to go through first. + + return true; + }); + + // We try to invoke the callbacks even if script parsing resulted in error... + m_jspebble->invokeCallbacks("ready"); + + loadJsFile(":/cacheLocalStorage.js"); + + if (m_configurationUuid == m_curApp.uuid()) { + qCDebug(l) << "going to launch config for" << m_configurationUuid; + showConfiguration(); + } + + m_configurationUuid = QUuid(); +} + +void JSKitManager::stopJsApp() +{ + qCDebug(l) << "stop js app" << m_curApp.uuid(); + if (!m_engine) return; // Nothing to do! + + loadJsFile(":/cacheLocalStorage.js"); + + if (!m_curApp.uuid().isNull()) { + m_appmsg->clearMessageHandler(m_curApp.uuid()); + } + + m_engine->collectGarbage(); + m_engine->deleteLater(); + m_engine = 0; +} |
