aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAkshay Nair <phenax5@gmail.com>2025-07-26 16:59:35 +0530
committerAkshay Nair <phenax5@gmail.com>2025-07-26 16:59:35 +0530
commitb0f598910f1f8bc8db6b65a0f01b1950ed7c3841 (patch)
tree234758ed1c91238b9498fdc6dd4bcccc42bddc36
parent8ab1f8d796d8f35a65e38fdd03d51824a41d1dab (diff)
downloadnull-browser-b0f598910f1f8bc8db6b65a0f01b1950ed7c3841.tar.gz
null-browser-b0f598910f1f8bc8db6b65a0f01b1950ed7c3841.zip
Add web.view.expose for simple lua->js interop in decorations
-rw-r--r--lua/null-browser/api.lua21
-rw-r--r--lua/null-browser/extras/tabline.lua14
-rw-r--r--src/LuaRuntime.hpp5
-rw-r--r--src/LuaRuntimeApi.hpp35
-rw-r--r--src/WindowActionRouter.cpp8
-rw-r--r--src/schemes/NullRpcSchemeHandler.hpp4
-rw-r--r--src/schemes/schemes.hpp8
-rw-r--r--src/widgets/BrowserWindow.cpp5
-rw-r--r--src/widgets/BrowserWindow.hpp1
-rw-r--r--src/widgets/Decorations.cpp7
-rw-r--r--src/widgets/Decorations.hpp4
-rw-r--r--src/widgets/EdgeDecoration.cpp12
-rw-r--r--src/widgets/EdgeDecoration.hpp1
-rw-r--r--src/widgets/IWebViewMediator.hpp4
-rw-r--r--src/widgets/WebViewStack.cpp5
-rw-r--r--src/widgets/WebViewStack.hpp2
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<void()> 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<WindowId> 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<void()> &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<WebViewId> 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<WebViewId> 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<WebViewId> 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<WebViewId> 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);