From b0f598910f1f8bc8db6b65a0f01b1950ed7c3841 Mon Sep 17 00:00:00 2001 From: Akshay Nair Date: Sat, 26 Jul 2025 16:59:35 +0530 Subject: Add web.view.expose for simple lua->js interop in decorations --- lua/null-browser/api.lua | 21 +++++++++++++++++++++ lua/null-browser/extras/tabline.lua | 14 +++++++++++--- src/LuaRuntime.hpp | 5 ++++- src/LuaRuntimeApi.hpp | 35 ++++++++++++++++++++++++++++++++++- src/WindowActionRouter.cpp | 8 ++++++-- src/schemes/NullRpcSchemeHandler.hpp | 4 +++- src/schemes/schemes.hpp | 8 ++++---- src/widgets/BrowserWindow.cpp | 5 +++++ src/widgets/BrowserWindow.hpp | 1 + src/widgets/Decorations.cpp | 7 +++++++ src/widgets/Decorations.hpp | 4 +++- src/widgets/EdgeDecoration.cpp | 12 +++++++++--- src/widgets/EdgeDecoration.hpp | 1 + src/widgets/IWebViewMediator.hpp | 4 +++- src/widgets/WebViewStack.cpp | 5 +++++ src/widgets/WebViewStack.hpp | 2 ++ 16 files changed, 119 insertions(+), 17 deletions(-) diff --git a/lua/null-browser/api.lua b/lua/null-browser/api.lua index 598594e..07b7072 100644 --- a/lua/null-browser/api.lua +++ b/lua/null-browser/api.lua @@ -129,6 +129,27 @@ function web.view.set_url(url, view_id) return __internals.view_set_url(url, vie --- ``` function web.view.set_html(html, opts) return __internals.view_set_html(html, (opts or {}).view) end +--- @class ExposeOpts +--- @field view? number View id + +--- Expose a lua function inside a view (Only works with decorations) +--- +--- @param name string Func name +--- @param action fun(table):nil Action to call when function is invoked in view +--- @param opts? ExposeOpts Options +--- +--- @example +--- ```lua +--- web.view.expose('tab_select', function(args) +--- web.view.select(tonumber(args.view)) +--- end) +--- ``` +--- then in js +--- ```js +--- __nullbrowser.tab_select({ view: 5 }) +--- ``` +function web.view.expose(name, action, opts) return __internals.view_expose(name, action, (opts or {}).view) end + --- Open devtools window for the view --- --- @param view_id? number Id of the view diff --git a/lua/null-browser/extras/tabline.lua b/lua/null-browser/extras/tabline.lua index e8edbae..b0d7761 100644 --- a/lua/null-browser/extras/tabline.lua +++ b/lua/null-browser/extras/tabline.lua @@ -27,9 +27,17 @@ function tabline.show_tabs_in_window(win_id, decoration) callback = function() if not decoration.is_enabled({ win = win_id }) then return end - web.view.set_html(tabline.tabs_html(), { - view = decoration.view({ win = win_id }), - }) + local view = decoration.view({ win = win_id }) + + web.view.expose('tab_select', function(args) + print(web.inspect(args)) + local view_id = args and args.view and tonumber(args.view) + if view_id then + web.view.select(view_id) + end + end, { view = view }) + + web.view.set_html(tabline.tabs_html(), { view = view }) end, }) end diff --git a/src/LuaRuntime.hpp b/src/LuaRuntime.hpp index 5d8e7cc..0e4584d 100644 --- a/src/LuaRuntime.hpp +++ b/src/LuaRuntime.hpp @@ -10,6 +10,7 @@ #include "utils.hpp" #include "widgets/BrowserWindow.hpp" #include "widgets/Decorations.hpp" +#include "widgets/WebView.hpp" #include "widgets/WebViewStack.hpp" #ifndef PROJECT_LUA_PATH @@ -47,6 +48,7 @@ signals: void evaluation_completed(QVariant value); void evaluation_failed(QString value); + // TODO: Figure out consistent naming for these signals void history_back_requested(WebViewId webview_id, qsizetype history_index); void history_forward_requested(WebViewId webview_id, qsizetype history_index); void keymap_add_requested(QString mode, QString keyseq, std::function action); @@ -63,8 +65,9 @@ signals: void webview_scroll_top_requested(WebViewId webview_id); void webview_scroll_bottom_requested(WebViewId webview_id); void decoration_set_enabled(DecorationType type, bool enabled, std::optional win_id); - void set_view_html(const QString &html, WebViewId view_id); + void webview_html_set_requested(const QString &html, WebViewId view_id); void schedule_for_next_tick(const std::function &action); + void webview_rpc_action_defined(const QString &name, const RpcFunc &action, WebViewId view_id); protected: LuaRuntime(); diff --git a/src/LuaRuntimeApi.hpp b/src/LuaRuntimeApi.hpp index 06323f0..41d3364 100644 --- a/src/LuaRuntimeApi.hpp +++ b/src/LuaRuntimeApi.hpp @@ -9,6 +9,7 @@ #include "lua.h" #include "widgets/BrowserWindow.hpp" #include "widgets/Decorations.hpp" +#include "widgets/WebView.hpp" int lua_api_schedule_fn(lua_State *state) { lua_pushvalue(state, 1); @@ -43,7 +44,7 @@ int lua_api_view_set_html(lua_State *state) { const char *html = lua_tostring(state, 1); WebViewId view_id = lua_isnoneornil(state, 2) ? 0 : lua_tointeger(state, 2); auto &runtime = LuaRuntime::instance(); - emit runtime.set_view_html(html, view_id); + emit runtime.webview_html_set_requested(html, view_id); return 1; } @@ -331,6 +332,37 @@ int lua_api_decorations_get_view(lua_State *state) { return 1; } +// :: string -> (table -> nil) -> WebViewId -> nil +int lua_api_view_expose(lua_State *state) { + const char *name = lua_tostring(state, 1); + WebViewId view_id = lua_isnoneornil(state, 3) ? 0 : lua_tointeger(state, 3); + + lua_pushvalue(state, 2); + const int function_ref = luaL_ref(state, LUA_REGISTRYINDEX); + auto action = [state, function_ref](const RpcArgs &args) { + preserve_top(state, { + lua_rawgeti(state, LUA_REGISTRYINDEX, function_ref); + lua_newtable(state); + for (auto &pair : args) { + lua_pushstring(state, pair.first.toStdString().c_str()); + lua_pushstring(state, pair.second.toString().toStdString().c_str()); + lua_settable(state, -3); + } + if (lua_pcall(state, 1, 0, 0) != LUA_OK) { + const char *error = lua_tostring(state, -1); + qDebug() << "Error calling Lua function:" << error; + } + }) + }; + // TODO: Check if we need to cleanup function ref after webview done? + + auto &runtime = LuaRuntime::instance(); + emit runtime.webview_rpc_action_defined(name, action, view_id); + + lua_pushnil(state); + return 1; +} + // NOLINTNEXTLINE static luaL_Reg internals_api[] = { luaL_Reg{"event_add_listener", &lua_event_register}, @@ -350,6 +382,7 @@ static luaL_Reg internals_api[] = { luaL_Reg{"view_scroll", &lua_api_view_scroll}, luaL_Reg{"view_scroll_to_top", &lua_api_view_scroll_top}, luaL_Reg{"view_scroll_to_bottom", &lua_api_view_scroll_bottom}, + luaL_Reg{"view_expose", &lua_api_view_expose}, luaL_Reg{"history_back", &lua_history_back}, luaL_Reg{"history_forward", &lua_history_forward}, luaL_Reg{"search_get_text", &lua_api_search_get_text}, diff --git a/src/WindowActionRouter.cpp b/src/WindowActionRouter.cpp index 99d9931..3aeabc7 100644 --- a/src/WindowActionRouter.cpp +++ b/src/WindowActionRouter.cpp @@ -6,7 +6,6 @@ #include "LuaRuntime.hpp" #include "keymap/KeymapEvaluator.hpp" #include "widgets/BrowserWindow.hpp" -#include "widgets/WebViewStack.hpp" #include "WindowActionRouter.hpp" @@ -59,7 +58,7 @@ void WindowActionRouter::initialize(Configuration *config) { WITH_WEBVIEW_WINDOW(webview_id, window, { window->open_url(url, open_type, webview_id); }); }); - connect(&runtime, &LuaRuntime::set_view_html, this, + connect(&runtime, &LuaRuntime::webview_html_set_requested, this, [this](const QString &html, WebViewId webview_id) { WITH_WEBVIEW_WINDOW(webview_id, window, { window->set_html(html, webview_id); }); }); @@ -69,6 +68,11 @@ void WindowActionRouter::initialize(Configuration *config) { connect(&runtime, &LuaRuntime::webview_selected, this, [this](WebViewId webview_id) { WITH_WEBVIEW_WINDOW(webview_id, window, { window->select_webview(webview_id); }); }); + connect(&runtime, &LuaRuntime::webview_rpc_action_defined, this, + [this](const QString &name, const RpcFunc &action, WebViewId webview_id) { + WITH_WEBVIEW_WINDOW(webview_id, window, + { window->expose_rpc_function(name, action, webview_id); }); + }); // Search connect(&runtime, &LuaRuntime::search_requested, this, diff --git a/src/schemes/NullRpcSchemeHandler.hpp b/src/schemes/NullRpcSchemeHandler.hpp index b1d5168..ef9cd09 100644 --- a/src/schemes/NullRpcSchemeHandler.hpp +++ b/src/schemes/NullRpcSchemeHandler.hpp @@ -16,7 +16,8 @@ public: void requestStarted(QWebEngineUrlRequestJob *job) override { auto url = job->requestUrl(); - qDebug() << "REQ" << url << url.host(); + qDebug() << "REQ" << url << "From: " << job->initiator(); + // TODO: Validate origin if (url.host().isEmpty() || url.host() == "noop") { job->reply("text/html", new QBuffer(job)); @@ -32,6 +33,7 @@ public: QUrlQuery query(url.query()); emit message_received(url.host(), query); + // TODO: responses managed with request ids QByteArray data = "{}"; QBuffer *buffer = new QBuffer(job); buffer->setData(data); diff --git a/src/schemes/schemes.hpp b/src/schemes/schemes.hpp index f04dc4d..f33d74b 100644 --- a/src/schemes/schemes.hpp +++ b/src/schemes/schemes.hpp @@ -5,11 +5,11 @@ void register_nullrpc_scheme() { QWebEngineUrlScheme scheme("nullrpc"); - scheme.setFlags( - QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::LocalScheme | - QWebEngineUrlScheme::LocalAccessAllowed | QWebEngineUrlScheme::ServiceWorkersAllowed | - QWebEngineUrlScheme::ContentSecurityPolicyIgnored | QWebEngineUrlScheme::FetchApiAllowed); scheme.setSyntax(QWebEngineUrlScheme::Syntax::Path); + scheme.setFlags(QWebEngineUrlScheme::SecureScheme | QWebEngineUrlScheme::LocalScheme | + QWebEngineUrlScheme::LocalAccessAllowed | + QWebEngineUrlScheme::ContentSecurityPolicyIgnored | + QWebEngineUrlScheme::FetchApiAllowed); QWebEngineUrlScheme::registerScheme(scheme); } diff --git a/src/widgets/BrowserWindow.cpp b/src/widgets/BrowserWindow.cpp index f97f397..1fcf0c0 100644 --- a/src/widgets/BrowserWindow.cpp +++ b/src/widgets/BrowserWindow.cpp @@ -97,3 +97,8 @@ void BrowserWindow::open_url(const QUrl &url, OpenType open_type, WebViewId webv void BrowserWindow::set_html(const QString &html, WebViewId webview_id) { get_webview_mediator(webview_id)->set_html(html, webview_id); } + +void BrowserWindow::expose_rpc_function(const QString &name, const RpcFunc &action, + WebViewId webview_id) { + get_webview_mediator(webview_id)->expose_rpc_function(name, action, webview_id); +} diff --git a/src/widgets/BrowserWindow.hpp b/src/widgets/BrowserWindow.hpp index e434521..3dc9e69 100644 --- a/src/widgets/BrowserWindow.hpp +++ b/src/widgets/BrowserWindow.hpp @@ -38,6 +38,7 @@ 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 expose_rpc_function(const QString &name, const RpcFunc &action, WebViewId webview_id); bool on_window_key_event(QKeyEvent *event); diff --git a/src/widgets/Decorations.cpp b/src/widgets/Decorations.cpp index 223e19a..7886442 100644 --- a/src/widgets/Decorations.cpp +++ b/src/widgets/Decorations.cpp @@ -88,3 +88,10 @@ std::optional Decorations::get_view_id(DecorationType type) { auto decoration = get_decoration_widget_type(type); return decoration.has_value() ? decoration.value()->get_view_id() : std::nullopt; } + +void Decorations::expose_rpc_function(const QString &name, const RpcFunc &action, + WebViewId view_id) { + auto decoration = get_decoration_widget_by_view_id(view_id); + if (decoration.has_value()) + decoration.value()->expose_rpc_function(name, action); +} diff --git a/src/widgets/Decorations.hpp b/src/widgets/Decorations.hpp index ef81c19..9788359 100644 --- a/src/widgets/Decorations.hpp +++ b/src/widgets/Decorations.hpp @@ -27,8 +27,10 @@ public: std::optional get_view_id(DecorationType type); bool has_webview(WebViewId view_id) override; - void open_url(const QUrl &url, OpenType /*unused*/, 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 expose_rpc_function(const QString &name, const RpcFunc &action, + WebViewId webview_id) override; private: EdgeDecoration *decoration_top; diff --git a/src/widgets/EdgeDecoration.cpp b/src/widgets/EdgeDecoration.cpp index 5277e6c..ed48857 100644 --- a/src/widgets/EdgeDecoration.cpp +++ b/src/widgets/EdgeDecoration.cpp @@ -66,9 +66,9 @@ void EdgeDecoration::setup_webview() { webview = new WebView(WebViewStack::next_webview_id++, profile, this); layout()->addWidget(webview.value()); webview.value()->enable_rpc_api(); - webview.value()->expose_rpc_function("tab_select", [](RpcArgs args) { - LuaRuntime::instance().webview_selected(args.at("view").toInt()); - }); + // expose_rpc_function("tab_select", [](RpcArgs args) { + // LuaRuntime::instance().webview_selected(args.at("view").toInt()); + // }); } webview.value()->setHtml(QString(default_html_layout).replace("{{body}}", html_content), @@ -95,3 +95,9 @@ void EdgeDecoration::set_url(const QUrl &url) { std::optional EdgeDecoration::get_view_id() { return webview.has_value() ? std::make_optional(webview.value()->get_id()) : std::nullopt; } + +void EdgeDecoration::expose_rpc_function(const QString &name, const RpcFunc &action) { + if (!webview.has_value()) + return; + webview.value()->expose_rpc_function(name, action); +} diff --git a/src/widgets/EdgeDecoration.hpp b/src/widgets/EdgeDecoration.hpp index 0a977df..2af567c 100644 --- a/src/widgets/EdgeDecoration.hpp +++ b/src/widgets/EdgeDecoration.hpp @@ -22,6 +22,7 @@ public: void set_enabled(bool enabled_value); void set_url(const QUrl &url); std::optional get_view_id(); + void expose_rpc_function(const QString &name, const RpcFunc &action); DEFINE_GETTER(is_enabled, enabled) diff --git a/src/widgets/IWebViewMediator.hpp b/src/widgets/IWebViewMediator.hpp index d20836a..b9af36f 100644 --- a/src/widgets/IWebViewMediator.hpp +++ b/src/widgets/IWebViewMediator.hpp @@ -1,13 +1,15 @@ #pragma once #include "WebViewData.hpp" +#include "widgets/WebView.hpp" class IWebViewMediator { public: IWebViewMediator() = default; virtual bool has_webview(WebViewId webview_id) = 0; - virtual void open_url(const QUrl &url, OpenType open_type, WebViewId webview_id) = 0; 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; }; diff --git a/src/widgets/WebViewStack.cpp b/src/widgets/WebViewStack.cpp index 698cb88..9ec8570 100644 --- a/src/widgets/WebViewStack.cpp +++ b/src/widgets/WebViewStack.cpp @@ -328,3 +328,8 @@ void WebViewStack::set_html(const QString &html, WebViewId webview_id) { webview->setHtml(html); } + +void WebViewStack::expose_rpc_function(const QString &name, const RpcFunc & /* unused */, + WebViewId /* unused */) { + qDebug() << "expose_rpc_function: NOT IMPLEMENTED" << name; +} diff --git a/src/widgets/WebViewStack.hpp b/src/widgets/WebViewStack.hpp index 2096f25..27a1181 100644 --- a/src/widgets/WebViewStack.hpp +++ b/src/widgets/WebViewStack.hpp @@ -62,6 +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 expose_rpc_function(const QString &name, const RpcFunc &action, + WebViewId webview_id) override; protected slots: void on_new_webview_request(QWebEngineNewWindowRequest &request); -- cgit v1.3.1