diff options
| -rw-r--r-- | lua/null-browser/api.lua | 9 | ||||
| -rw-r--r-- | spec/LuaRuntimeApiSpec.cpp | 57 | ||||
| -rw-r--r-- | spec/LuaRuntimeSpec.cpp | 54 | ||||
| -rw-r--r-- | src/LuaRuntime.cpp | 37 | ||||
| -rw-r--r-- | src/LuaRuntime.hpp | 4 | ||||
| -rw-r--r-- | src/LuaRuntimeApi.hpp | 25 | ||||
| -rw-r--r-- | src/WindowActionRouter.cpp | 4 | ||||
| -rw-r--r-- | src/widgets/BrowserWindow.cpp | 5 | ||||
| -rw-r--r-- | src/widgets/BrowserWindow.hpp | 3 | ||||
| -rw-r--r-- | src/widgets/Decorations.cpp | 5 | ||||
| -rw-r--r-- | src/widgets/Decorations.hpp | 4 | ||||
| -rw-r--r-- | src/widgets/EdgeDecoration.cpp | 4 | ||||
| -rw-r--r-- | src/widgets/EdgeDecoration.hpp | 2 | ||||
| -rw-r--r-- | src/widgets/IWebViewMediator.hpp | 3 | ||||
| -rw-r--r-- | src/widgets/WebView.hpp | 3 | ||||
| -rw-r--r-- | src/widgets/WebViewStack.cpp | 5 | ||||
| -rw-r--r-- | src/widgets/WebViewStack.hpp | 3 |
17 files changed, 204 insertions, 23 deletions
diff --git a/lua/null-browser/api.lua b/lua/null-browser/api.lua index 65a7663..e1f2acc 100644 --- a/lua/null-browser/api.lua +++ b/lua/null-browser/api.lua @@ -125,14 +125,15 @@ function web.view.set_url(url, view_id) return __internals.view_set_url(url, vie --- @example --- ```lua --- web.view.set_html('<h2>HJello</h2>') ---- web.view.set_html('<h2>HJello</h2>', 3) -- Set html for view with id 3 +--- web.view.set_html('<h2>HJello</h2>', { view = 3 }) -- Set html for view with id 3 --- ``` function web.view.set_html(html, opts) return __internals.view_set_html(html, (opts or {}).view) end --- @class RunJSOpts --- @field view? number View id +--- @field on_result fun(v: any): nil Callback when ---- Run js in a given view +--- Run js in a view --- --- @param js string HTML string --- @param opts? RunJSOpts Options @@ -140,9 +141,9 @@ function web.view.set_html(html, opts) return __internals.view_set_html(html, (o --- @example --- ```lua --- web.view.run_js('console.log(42)') ---- web.view.run_js('console.log(42)', 3) -- Set html for view with id 3 +--- web.view.run_js('console.log(42)', { view = 3 }) -- Set html for view with id 3 --- ``` -function web.view.run_js(js, opts) return __internals.view_run_js(js, (opts or {}).view) end +function web.view.run_js(js, opts) return __internals.view_run_js(js, opts or {}) end --- @class ReloadOpts --- @field view? number View id diff --git a/spec/LuaRuntimeApiSpec.cpp b/spec/LuaRuntimeApiSpec.cpp index 123eaa1..028ceb6 100644 --- a/spec/LuaRuntimeApiSpec.cpp +++ b/spec/LuaRuntimeApiSpec.cpp @@ -401,7 +401,7 @@ private slots: } } - void test_lua_api_decorations_set_enabled() { + void test_lua_api_decorations_enable() { describe("web.decorations.*.enable"); context("when called without view id"); @@ -460,7 +460,9 @@ private slots: QCOMPARE(call[2].value<std::optional<WindowId>>(), std::make_optional(42)); } } + } + void test_lua_api_decorations_disable() { describe("web.decorations.*.disable"); context("when called without view id"); @@ -520,6 +522,59 @@ private slots: } } } + + void test_lua_api_view_run_js() { + describe("web.view.run_js"); + + context("when called without opts"); + it("runs js in current view") { + auto &lua = LuaRuntime::instance(); + QSignalSpy view_run_js(&lua, &LuaRuntime::webview_js_eval_requested); + + lua.evaluate(R"LUA( web.view.run_js("dostuff(200)") )LUA"); + + QVERIFY(view_run_js.wait()); + auto call = view_run_js.first(); + QCOMPARE(call[0], "dostuff(200)"); + QCOMPARE(call[1], 0); + QVERIFY(call[2].canConvert<JsOnResultFunc>()); // Noop function + } + + context("when called with opts.view"); + it("runs js in given view") { + auto &lua = LuaRuntime::instance(); + QSignalSpy view_run_js(&lua, &LuaRuntime::webview_js_eval_requested); + + lua.evaluate(R"LUA( web.view.run_js("dostuff(200)", { view = 42 }) )LUA"); + + QVERIFY(view_run_js.wait()); + auto call = view_run_js.first(); + QCOMPARE(call[0], "dostuff(200)"); + QCOMPARE(call[1], 42); + QVERIFY(call[2].canConvert<JsOnResultFunc>()); + } + + context("when called with opts.on_result"); + it("runs js in given view and calls on_result with the result") { + auto &lua = LuaRuntime::instance(); + QSignalSpy view_run_js(&lua, &LuaRuntime::webview_js_eval_requested); + + lua.evaluate(R"LUA( + _G.on_result_called_with = null + web.view.run_js("dostuff(200)", { view = 42, on_result = function(value) + _G.on_result_called_with = value + end }) + )LUA"); + + QVERIFY(view_run_js.wait()); + auto call = view_run_js.first(); + QCOMPARE(call[0], "dostuff(200)"); + QCOMPARE(call[1], 42); + QVERIFY(call[2].canConvert<JsOnResultFunc>()); + call[2].value<JsOnResultFunc>()(9921.29); + QVERIFY(wait_for_lua_to_be_true(R"LUA( return _G.on_result_called_with == 9921.29 )LUA")); + } + } }; QTEST_REGISTER(LuaRuntimeApiSpec) diff --git a/spec/LuaRuntimeSpec.cpp b/spec/LuaRuntimeSpec.cpp index aeaaab2..4d9270c 100644 --- a/spec/LuaRuntimeSpec.cpp +++ b/spec/LuaRuntimeSpec.cpp @@ -1,7 +1,9 @@ #include <QtCore> +#include <qtestcase.h> #include <uv.h> #include "LuaRuntime.hpp" +#include "lua.h" #include "testUtils.h" // NOLINTBEGIN @@ -124,6 +126,58 @@ private slots: QVERIFY(wait_for_lua_to_be_true("return _G.spawn_exit_code == 0")); } } + + void test_push_qvariant() { + describe("LuaRuntime::push_qvariant"); + + context("when called with a QString"); + it("pushes lua string") { + auto &lua = LuaRuntime::instance(); + auto *state = lua.get_state(); + + LuaRuntime::push_qvariant(state, QString("hello")); + + QVERIFY(lua_isstring(state, 1)); + QCOMPARE(lua_tostring(state, 1), "hello"); + lua_pop(state, 1); + } + + context("when called with a QVariant int"); + it("pushes lua number") { + auto &lua = LuaRuntime::instance(); + auto *state = lua.get_state(); + + LuaRuntime::push_qvariant(state, QVariant(42)); + + QVERIFY(lua_isnumber(state, 1)); + QCOMPARE(lua_tointeger(state, 1), 42); + lua_pop(state, 1); + } + + context("when called with a QVariant double"); + it("pushes lua number") { + auto &lua = LuaRuntime::instance(); + auto *state = lua.get_state(); + + LuaRuntime::push_qvariant(state, QVariant(42.69)); + + QVERIFY(lua_isnumber(state, 1)); + QCOMPARE(lua_tonumber(state, 1), 42.69); + lua_pop(state, 1); + } + + context("when called with a QVariant bool"); + it("pushes lua boolean") { + auto &lua = LuaRuntime::instance(); + auto *state = lua.get_state(); + + LuaRuntime::push_qvariant(state, QVariant(true)); + + QVERIFY(lua_isboolean(state, 1)); + QCOMPARE(lua_toboolean(state, 1), true); + lua_pop(state, 1); + } + } }; QTEST_REGISTER(LuaRuntimeSpec) diff --git a/src/LuaRuntime.cpp b/src/LuaRuntime.cpp index 343266c..76dfb11 100644 --- a/src/LuaRuntime.cpp +++ b/src/LuaRuntime.cpp @@ -3,6 +3,7 @@ #include <cstdlib> #include <cstring> #include <lua.hpp> +#include <optional> #include <qvariant.h> extern "C" { #include <luv/luv.h> @@ -157,3 +158,39 @@ QVariant LuaRuntime::get_lua_value(lua_State *state, int idx, QVariant default_v return lua_tostring(state, idx); } + +void LuaRuntime::push_qvariant(lua_State *state, std::optional<QVariant> opt) { + if (!opt.has_value()) { + lua_pushnil(state); + return; + } + + auto type = QString(opt.value().typeName()); + // qDebug() << type << opt.value(); + + // TODO: QVariantMap + // TODO: QVariantList + + if (type == "") { + lua_pushnil(state); + return; + } + + if (type == "bool") { + lua_pushboolean(state, opt.value().toBool()); + return; + } + + if (type == "int") { + lua_pushinteger(state, opt.value().toInt()); + return; + } + + if (type == "double") { + lua_pushnumber(state, opt.value().toDouble()); + return; + } + + // String default + lua_pushstring(state, opt.value().toString().toStdString().c_str()); +} diff --git a/src/LuaRuntime.hpp b/src/LuaRuntime.hpp index d5cd12c..78ae354 100644 --- a/src/LuaRuntime.hpp +++ b/src/LuaRuntime.hpp @@ -64,7 +64,8 @@ signals: void webview_scroll_bottom_requested(WebViewId webview_id); void decoration_set_enabled(DecorationType type, bool enabled, std::optional<WindowId> win_id); void webview_html_set_requested(const QString &html, WebViewId view_id); - void webview_js_eval_requested(const QString &js_code, WebViewId view_id); + void webview_js_eval_requested(const QString &js_code, WebViewId view_id, + const JsOnResultFunc &on_result); void schedule_for_next_tick(const std::function<void()> &action); void webview_rpc_action_defined(const QString &name, const RpcFunc &action, WebViewId view_id); void webview_reload_requested(WebViewId webview_id); @@ -83,4 +84,5 @@ public: static void inspect_lua_stack(lua_State *state); static std::vector<QString> lua_tostringlist(lua_State *state); static QVariant get_lua_value(lua_State *state, int idx, QVariant default_value = 0); + static void push_qvariant(lua_State *state, std::optional<QVariant> opt); }; diff --git a/src/LuaRuntimeApi.hpp b/src/LuaRuntimeApi.hpp index 0af1e9e..d6a78d5 100644 --- a/src/LuaRuntimeApi.hpp +++ b/src/LuaRuntimeApi.hpp @@ -49,9 +49,30 @@ int lua_api_view_set_html(lua_State *state) { int lua_api_view_run_js(lua_State *state) { const char *js_code = lua_tostring(state, 1); - WebViewId view_id = lua_isnoneornil(state, 2) ? 0 : lua_tointeger(state, 2); + + lua_getfield(state, 2, "view"); + WebViewId view_id = lua_isnoneornil(state, 3) ? 0 : lua_tointeger(state, 3); + lua_pop(state, 1); + + lua_getfield(state, 2, "on_result"); + JsOnResultFunc action = [](auto &) {}; + if (lua_isfunction(state, 3)) { + lua_pushvalue(state, 3); + const int function_ref = luaL_ref(state, LUA_REGISTRYINDEX); + action = [state, function_ref](const std::optional<QVariant> &value) { + preserve_top(state, { + lua_rawgeti(state, LUA_REGISTRYINDEX, function_ref); + LuaRuntime::push_qvariant(state, value); + lua_pcall(state, 1, 0, 0); + luaL_unref(state, LUA_REGISTRYINDEX, function_ref); + }) + }; + } + lua_pop(state, 1); + auto &runtime = LuaRuntime::instance(); - emit runtime.webview_js_eval_requested(js_code, view_id); + emit runtime.webview_js_eval_requested(js_code, view_id, action); + return 1; } diff --git a/src/WindowActionRouter.cpp b/src/WindowActionRouter.cpp index 558a46e..f513210 100644 --- a/src/WindowActionRouter.cpp +++ b/src/WindowActionRouter.cpp @@ -78,9 +78,9 @@ void WindowActionRouter::initialize(Configuration *config) { { window->expose_rpc_function(name, action, webview_id); }); }); connect(&runtime, &LuaRuntime::webview_js_eval_requested, this, - [this](const QString &js_code, WebViewId webview_id) { + [this](const QString &js_code, WebViewId webview_id, const JsOnResultFunc &on_result) { WITH_WEBVIEW_WINDOW(webview_id, window, - { window->run_javascript(js_code, webview_id); }); + { window->run_javascript(js_code, webview_id, on_result); }); }); connect(&runtime, &LuaRuntime::webview_reload_requested, this, [this](WebViewId webview_id) { WITH_WEBVIEW_WINDOW(webview_id, window, { window->reload(webview_id); }); diff --git a/src/widgets/BrowserWindow.cpp b/src/widgets/BrowserWindow.cpp index 87e315c..acb07f7 100644 --- a/src/widgets/BrowserWindow.cpp +++ b/src/widgets/BrowserWindow.cpp @@ -102,8 +102,9 @@ void BrowserWindow::set_html(const QString &html, WebViewId webview_id) { get_webview_mediator(webview_id)->set_html(html, webview_id); } -void BrowserWindow::run_javascript(const QString &js_code, WebViewId webview_id) { - get_webview_mediator(webview_id)->run_javascript(js_code, webview_id); +void BrowserWindow::run_javascript(const QString &js_code, WebViewId webview_id, + const JsOnResultFunc &on_result) { + get_webview_mediator(webview_id)->run_javascript(js_code, webview_id, on_result); } void BrowserWindow::expose_rpc_function(const QString &name, const RpcFunc &action, diff --git a/src/widgets/BrowserWindow.hpp b/src/widgets/BrowserWindow.hpp index de48c6e..2842983 100644 --- a/src/widgets/BrowserWindow.hpp +++ b/src/widgets/BrowserWindow.hpp @@ -38,7 +38,8 @@ public: bool has_webview(WebViewId webview_id); void open_url(const QUrl &url, OpenType open_type, WebViewId webview_id); void set_html(const QString &html, WebViewId webview_id); - void run_javascript(const QString &js_code, WebViewId webview_id); + void run_javascript(const QString &js_code, WebViewId webview_id, + const JsOnResultFunc &on_result); void expose_rpc_function(const QString &name, const RpcFunc &action, WebViewId webview_id); void reload(WebViewId webview_id); diff --git a/src/widgets/Decorations.cpp b/src/widgets/Decorations.cpp index 1cff3d4..22aa9f9 100644 --- a/src/widgets/Decorations.cpp +++ b/src/widgets/Decorations.cpp @@ -84,10 +84,11 @@ void Decorations::set_html(const QString &html, WebViewId view_id) { decoration.value()->set_html(html); } -void Decorations::run_javascript(const QString &js_code, WebViewId view_id) { +void Decorations::run_javascript(const QString &js_code, WebViewId view_id, + const JsOnResultFunc &on_result) { auto decoration = get_decoration_widget_by_view_id(view_id); if (decoration.has_value()) - decoration.value()->run_javascript(js_code); + decoration.value()->run_javascript(js_code, on_result); } std::optional<WebViewId> Decorations::get_view_id(DecorationType type) { diff --git a/src/widgets/Decorations.hpp b/src/widgets/Decorations.hpp index 108b13e..3df4fd7 100644 --- a/src/widgets/Decorations.hpp +++ b/src/widgets/Decorations.hpp @@ -3,6 +3,7 @@ #include "WebViewData.hpp" #include "widgets/EdgeDecoration.hpp" #include "widgets/IWebViewMediator.hpp" +#include "widgets/WebView.hpp" #include <QWidget> #include <QtCore> #include <optional> @@ -29,7 +30,8 @@ public: bool has_webview(WebViewId view_id) override; void open_url(const QUrl &url, OpenType open_type, WebViewId view_id) override; void set_html(const QString &html, WebViewId view_id) override; - void run_javascript(const QString &js_code, WebViewId webview_id) override; + void run_javascript(const QString &js_code, WebViewId webview_id, + const JsOnResultFunc &on_result) override; void expose_rpc_function(const QString &name, const RpcFunc &action, WebViewId webview_id) override; void reload(WebViewId webview_id) override; diff --git a/src/widgets/EdgeDecoration.cpp b/src/widgets/EdgeDecoration.cpp index bb114bf..76974be 100644 --- a/src/widgets/EdgeDecoration.cpp +++ b/src/widgets/EdgeDecoration.cpp @@ -94,10 +94,10 @@ void EdgeDecoration::set_url(const QUrl &url) { webview.value()->setUrl(url); } -void EdgeDecoration::run_javascript(const QString &js_code) { +void EdgeDecoration::run_javascript(const QString &js_code, const JsOnResultFunc &on_result) { if (!webview.has_value()) return; - webview.value()->run_javascript(js_code); + webview.value()->run_javascript(js_code, on_result); } std::optional<WebViewId> EdgeDecoration::get_view_id() { diff --git a/src/widgets/EdgeDecoration.hpp b/src/widgets/EdgeDecoration.hpp index 2c92de7..ed64658 100644 --- a/src/widgets/EdgeDecoration.hpp +++ b/src/widgets/EdgeDecoration.hpp @@ -21,7 +21,7 @@ public: void set_html(const QString &content); void set_enabled(bool enabled_value); void set_url(const QUrl &url); - void run_javascript(const QString &js_code); + void run_javascript(const QString &js_code, const JsOnResultFunc &on_result); std::optional<WebViewId> get_view_id(); void expose_rpc_function(const QString &name, const RpcFunc &action); diff --git a/src/widgets/IWebViewMediator.hpp b/src/widgets/IWebViewMediator.hpp index a7007f2..a5a8fdb 100644 --- a/src/widgets/IWebViewMediator.hpp +++ b/src/widgets/IWebViewMediator.hpp @@ -12,6 +12,7 @@ public: virtual void set_html(const QString &html, WebViewId webview_id) = 0; virtual void expose_rpc_function(const QString &name, const RpcFunc &action, WebViewId webview_id) = 0; - virtual void run_javascript(const QString &js_code, WebViewId webview_id) = 0; + virtual void run_javascript(const QString &js_code, WebViewId webview_id, + const JsOnResultFunc &on_result) = 0; virtual void reload(WebViewId webview_id) = 0; }; diff --git a/src/widgets/WebView.hpp b/src/widgets/WebView.hpp index 36a252d..0f2bd43 100644 --- a/src/widgets/WebView.hpp +++ b/src/widgets/WebView.hpp @@ -20,6 +20,8 @@ using RpcArgs = std::unordered_map<QString, QVariant>; using RpcFunc = std::function<void(RpcArgs)>; +using JsOnResultFunc = std::function<void(const std::optional<QVariant> &value)>; + class WebView : public QWebEngineView { Q_OBJECT @@ -31,6 +33,7 @@ public: void scroll_to_bottom(); void enable_rpc_api(); void expose_rpc_function(const QString &name, const RpcFunc &action); + // void run_javascript(const QString &code, const JsOnResultFunc &on_result); DELEGATE(page(), runJavaScript, run_javascript) DEFINE_GETTER(get_id, id) diff --git a/src/widgets/WebViewStack.cpp b/src/widgets/WebViewStack.cpp index ab5808f..5503a3c 100644 --- a/src/widgets/WebViewStack.cpp +++ b/src/widgets/WebViewStack.cpp @@ -342,14 +342,15 @@ void WebViewStack::set_html(const QString &html, WebViewId webview_id) { webview->setHtml(html); } -void WebViewStack::run_javascript(const QString &js_code, WebViewId webview_id) { +void WebViewStack::run_javascript(const QString &js_code, WebViewId webview_id, + const JsOnResultFunc &on_result) { auto *webview = get_webview(webview_id); if (webview == nullptr) { qDebug() << "Webview does not exist"; return; } - webview->run_javascript(js_code); + webview->run_javascript(js_code, on_result); } void WebViewStack::expose_rpc_function(const QString &name, const RpcFunc & /* unused */, diff --git a/src/widgets/WebViewStack.hpp b/src/widgets/WebViewStack.hpp index bbee44c..cbff0be 100644 --- a/src/widgets/WebViewStack.hpp +++ b/src/widgets/WebViewStack.hpp @@ -62,7 +62,8 @@ public slots: void scroll_to_top(WebViewId webview_id); void scroll_to_bottom(WebViewId webview_id); void set_html(const QString &html, WebViewId webview_id = 0) override; - void run_javascript(const QString &js_code, WebViewId webview_id) override; + void run_javascript(const QString &js_code, WebViewId webview_id, + const JsOnResultFunc &on_result) override; void expose_rpc_function(const QString &name, const RpcFunc &action, WebViewId webview_id) override; void reload(WebViewId webview_id) override; |
