v0.0.18 - Add real new window support for target=_blank and Open in New Window — uses webkit_web_view_new_with_related_view() to share WebProcess, window focus tracking for MCP active webview
This commit is contained in:
+211
-14
@@ -61,6 +61,12 @@ static GtkWidget *g_notebook = NULL;
|
||||
static WebKitWebContext *g_ctx = NULL;
|
||||
static GtkWindow *g_window = NULL;
|
||||
|
||||
/* Active window/notebook for MCP get_active_webview().
|
||||
* When a new window gets focus, these are updated so MCP tools operate
|
||||
* on the focused window's webview. Defaults to the main window/notebook. */
|
||||
static GtkWindow *g_active_window = NULL;
|
||||
static GtkWidget *g_active_notebook = NULL;
|
||||
|
||||
/* Dynamic array of tab_info_t pointers, indexed by notebook page number. */
|
||||
static tab_info_t **g_tabs = NULL;
|
||||
static int g_tab_count = 0;
|
||||
@@ -74,6 +80,28 @@ static GtkWidget *build_hamburger_menu(tab_info_t *tab);
|
||||
static char *normalize_url(const char *input);
|
||||
static void on_tab_close_clicked_proxy_new(GtkMenuItem *item, gpointer data);
|
||||
static void on_avatar_clicked(GtkButton *btn, gpointer data);
|
||||
static GtkWidget *tab_manager_new_window(const char *url,
|
||||
WebKitWebView *related_view);
|
||||
static gboolean on_window_focus_in(GtkWidget *widget,
|
||||
GdkEventFocus *event,
|
||||
gpointer user_data);
|
||||
static void on_aux_window_destroy(GtkWidget *widget,
|
||||
gpointer user_data);
|
||||
/* Forward declarations for signal handlers used by tab_manager_new_window
|
||||
* (which is defined before these handlers in the file). */
|
||||
static void on_load_changed(WebKitWebView *webview,
|
||||
WebKitLoadEvent load_event,
|
||||
gpointer user_data);
|
||||
static gboolean on_load_failed(WebKitWebView *webview,
|
||||
WebKitLoadEvent load_event,
|
||||
gchar *failing_uri,
|
||||
GError *error,
|
||||
gpointer data);
|
||||
static gboolean on_webview_context_menu(WebKitWebView *webview,
|
||||
WebKitContextMenu *context_menu,
|
||||
GdkEvent *event,
|
||||
WebKitHitTestResult *hit_test,
|
||||
gpointer user_data);
|
||||
|
||||
/* ── URL bar completion (search dropdown) ─────────────────────────── *
|
||||
* Each tab's URL entry has a GtkEntryCompletion backed by a GtkListStore.
|
||||
@@ -650,30 +678,188 @@ static gboolean on_decide_policy(WebKitWebView *webview,
|
||||
static GtkWidget *on_create_webview(WebKitWebView *webview,
|
||||
WebKitNavigationAction *action,
|
||||
gpointer user_data) {
|
||||
(void)webview;
|
||||
(void)user_data;
|
||||
|
||||
WebKitURIRequest *request = webkit_navigation_action_get_request(action);
|
||||
const char *uri = webkit_uri_request_get_uri(request);
|
||||
|
||||
/* When a page requests a new window (target="_blank"), WebKit fires
|
||||
* the "create" signal. We intercept it by opening the URI in a new
|
||||
* tab ourselves, then return NULL to tell WebKit we handled it.
|
||||
/* When a page requests a new window (target="_blank", window.open()),
|
||||
* WebKit fires the "create" signal. We create a real GtkWindow with
|
||||
* its own webview (created with webkit_web_view_new_with_related_view
|
||||
* so it shares the parent's WebProcess) and return that webview.
|
||||
*
|
||||
* Returning a webview here causes a crash in WebKit's
|
||||
* std::optional<WindowFeatures> assertion because the returned
|
||||
* webview doesn't have the expected window features set up.
|
||||
* By returning NULL and handling the navigation ourselves, we
|
||||
* avoid the crash entirely. */
|
||||
* Using webkit_web_view_new_with_related_view() shares the WebProcess
|
||||
* and the related view's window features, which avoids the previous
|
||||
* std::optional<WindowFeatures> assertion crash that occurred when
|
||||
* returning a webview created with webkit_web_view_new_with_context().
|
||||
*
|
||||
* If new window creation fails, fall back to opening a tab. */
|
||||
if (uri && uri[0]) {
|
||||
GtkWidget *new_wv = tab_manager_new_window(uri, webview);
|
||||
if (new_wv != NULL) {
|
||||
return new_wv;
|
||||
}
|
||||
/* Fallback: open in a new tab in the main window. */
|
||||
tab_manager_new_tab(uri);
|
||||
}
|
||||
|
||||
/* Return NULL to indicate we handled the creation ourselves.
|
||||
* WebKit will not proceed with the navigation in a new view. */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ── New window creation (real GtkWindow for target="_blank") ──────── *
|
||||
* Creates a new top-level GtkWindow with its own GtkNotebook and a
|
||||
* single webview. The webview is created with
|
||||
* webkit_web_view_new_with_related_view(related_view) when a parent
|
||||
* webview is available, so it shares the parent's WebProcess and
|
||||
* window features — this avoids the std::optional<WindowFeatures>
|
||||
* assertion crash that occurred with webkit_web_view_new_with_context().
|
||||
*
|
||||
* The new window's webview gets the same setup as a regular tab:
|
||||
* WebKitSettings, nostr_inject_setup, key-press-event, decide-policy,
|
||||
* create, load-changed, etc. The window does NOT quit the app when
|
||||
* closed — only the main window (in main.c) does that.
|
||||
*
|
||||
* Returns the new WebKitWebView widget, or NULL on failure.
|
||||
*/
|
||||
|
||||
/* focus-in-event handler for auxiliary windows: updates the global
|
||||
* active window/notebook pointers so MCP get_active_webview() resolves
|
||||
* to the focused window's webview. */
|
||||
static gboolean on_window_focus_in(GtkWidget *widget,
|
||||
GdkEventFocus *event,
|
||||
gpointer user_data) {
|
||||
(void)event;
|
||||
GtkWidget *notebook = GTK_WIDGET(user_data);
|
||||
if (widget == NULL || notebook == NULL) return FALSE;
|
||||
|
||||
g_active_window = GTK_WINDOW(widget);
|
||||
g_active_notebook = notebook;
|
||||
g_print("[windows] Active window changed to %p (notebook %p)\n",
|
||||
(void *)g_active_window, (void *)g_active_notebook);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* destroy handler for auxiliary windows: if this was the active window,
|
||||
* fall back to the main window/notebook so MCP keeps working. Does NOT
|
||||
* quit the app — only the main window's destroy handler does that. */
|
||||
static void on_aux_window_destroy(GtkWidget *widget, gpointer user_data) {
|
||||
(void)user_data;
|
||||
if (g_active_window == GTK_WINDOW(widget)) {
|
||||
g_active_window = g_window;
|
||||
g_active_notebook = g_notebook;
|
||||
g_print("[windows] Active window closed, reverting to main window\n");
|
||||
}
|
||||
g_print("[windows] Auxiliary window destroyed: %p\n", (void *)widget);
|
||||
}
|
||||
|
||||
static GtkWidget *tab_manager_new_window(const char *url,
|
||||
WebKitWebView *related_view) {
|
||||
if (g_ctx == NULL) return NULL;
|
||||
|
||||
/* Create the top-level window. */
|
||||
GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
|
||||
gtk_window_set_title(GTK_WINDOW(window), "sovereign browser");
|
||||
gtk_window_set_default_size(GTK_WINDOW(window), 1024, 768);
|
||||
|
||||
/* Create a notebook for this window (so future tabs could be added;
|
||||
* for now it holds a single page). */
|
||||
GtkWidget *notebook = gtk_notebook_new();
|
||||
gtk_notebook_set_scrollable(GTK_NOTEBOOK(notebook), FALSE);
|
||||
gtk_notebook_set_show_border(GTK_NOTEBOOK(notebook), FALSE);
|
||||
gtk_notebook_set_show_tabs(GTK_NOTEBOOK(notebook), TRUE);
|
||||
gtk_container_add(GTK_CONTAINER(window), notebook);
|
||||
|
||||
/* Create the webview. Prefer webkit_web_view_new_with_related_view
|
||||
* so the new webview shares the parent's WebProcess and window
|
||||
* features (avoids the WindowFeatures assertion crash). Fall back
|
||||
* to the shared context if no related view is provided. */
|
||||
WebKitWebView *wv = NULL;
|
||||
if (related_view != NULL && WEBKIT_IS_WEB_VIEW(related_view)) {
|
||||
wv = WEBKIT_WEB_VIEW(webkit_web_view_new_with_related_view(related_view));
|
||||
} else {
|
||||
wv = WEBKIT_WEB_VIEW(webkit_web_view_new_with_context(g_ctx));
|
||||
}
|
||||
if (wv == NULL) {
|
||||
gtk_widget_destroy(window);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Apply the same WebKitSettings as regular tabs. */
|
||||
WebKitSettings *settings = webkit_web_view_get_settings(wv);
|
||||
webkit_settings_set_enable_developer_extras(settings, TRUE);
|
||||
webkit_settings_set_enable_javascript(settings, TRUE);
|
||||
webkit_settings_set_javascript_can_open_windows_automatically(settings, TRUE);
|
||||
webkit_settings_set_allow_file_access_from_file_urls(settings, TRUE);
|
||||
webkit_settings_set_allow_universal_access_from_file_urls(settings, TRUE);
|
||||
webkit_settings_set_allow_modal_dialogs(settings, TRUE);
|
||||
|
||||
/* Inject window.nostr into this webview. */
|
||||
nostr_inject_setup(wv);
|
||||
|
||||
/* Connect the key-press handler (browser shortcuts). */
|
||||
g_signal_connect(G_OBJECT(wv), "key-press-event",
|
||||
G_CALLBACK(on_key_press), NULL);
|
||||
|
||||
/* Ensure the webview expands to fill the window. */
|
||||
gtk_widget_set_vexpand(GTK_WIDGET(wv), TRUE);
|
||||
gtk_widget_set_hexpand(GTK_WIDGET(wv), TRUE);
|
||||
|
||||
/* Build a minimal tab label for the notebook page. */
|
||||
GtkWidget *label_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||
GtkWidget *title_lbl = gtk_label_new("Loading…");
|
||||
gtk_label_set_ellipsize(GTK_LABEL(title_lbl), PANGO_ELLIPSIZE_END);
|
||||
gtk_label_set_max_width_chars(GTK_LABEL(title_lbl), 20);
|
||||
gtk_widget_set_hexpand(title_lbl, TRUE);
|
||||
gtk_box_pack_start(GTK_BOX(label_box), title_lbl, TRUE, TRUE, 0);
|
||||
|
||||
/* Add the webview as a notebook page. */
|
||||
gtk_notebook_append_page(GTK_NOTEBOOK(notebook), GTK_WIDGET(wv),
|
||||
label_box);
|
||||
gtk_notebook_set_tab_reorderable(GTK_NOTEBOOK(notebook), GTK_WIDGET(wv),
|
||||
TRUE);
|
||||
|
||||
/* Wire the same webview signals as tab_create(). We pass NULL as
|
||||
* user_data for handlers that expect a tab_info_t* — those handlers
|
||||
* only dereference user_data for UI updates (URL bar, tab title,
|
||||
* favicon) that don't apply to a standalone window webview. The
|
||||
* navigation-relevant handlers (decide-policy, create, load-failed)
|
||||
* don't need the tab pointer. */
|
||||
g_signal_connect(wv, "load-changed",
|
||||
G_CALLBACK(on_load_changed), NULL);
|
||||
g_signal_connect(wv, "load-failed",
|
||||
G_CALLBACK(on_load_failed), NULL);
|
||||
g_signal_connect(wv, "decide-policy",
|
||||
G_CALLBACK(on_decide_policy), NULL);
|
||||
g_signal_connect(wv, "create",
|
||||
G_CALLBACK(on_create_webview), NULL);
|
||||
g_signal_connect(wv, "context-menu",
|
||||
G_CALLBACK(on_webview_context_menu), NULL);
|
||||
|
||||
/* Window lifecycle: focus-in updates the active window/notebook
|
||||
* pointers; destroy reverts to the main window but does NOT quit. */
|
||||
g_signal_connect(window, "focus-in-event",
|
||||
G_CALLBACK(on_window_focus_in), notebook);
|
||||
g_signal_connect(window, "destroy",
|
||||
G_CALLBACK(on_aux_window_destroy), NULL);
|
||||
|
||||
/* Load the URL. */
|
||||
if (url && url[0]) {
|
||||
webkit_web_view_load_uri(wv, url);
|
||||
}
|
||||
|
||||
/* Show everything and present the window. */
|
||||
gtk_widget_show_all(window);
|
||||
gtk_window_present(GTK_WINDOW(window));
|
||||
|
||||
/* This new window is now the active window. */
|
||||
g_active_window = GTK_WINDOW(window);
|
||||
g_active_notebook = notebook;
|
||||
|
||||
g_print("[windows] Created new window %p for %s\n",
|
||||
(void *)window, url ? url : "(none)");
|
||||
return GTK_WIDGET(wv);
|
||||
}
|
||||
|
||||
static GtkWidget *build_tab_label(tab_info_t *tab) {
|
||||
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
|
||||
|
||||
@@ -829,7 +1015,11 @@ static void on_load_changed(WebKitWebView *webview,
|
||||
gpointer user_data) {
|
||||
tab_info_t *tab = (tab_info_t *)user_data;
|
||||
|
||||
/* tab is NULL for webviews in auxiliary (new) windows, which don't
|
||||
* have a tab_info_t. Skip the tab-UI updates (URL bar, favicon, tab
|
||||
* title) but still record history on load-finished below. */
|
||||
if (load_event == WEBKIT_LOAD_COMMITTED) {
|
||||
if (tab == NULL) return;
|
||||
const gchar *uri = webkit_web_view_get_uri(webview);
|
||||
if (uri != NULL) {
|
||||
/* Clear the favicon — the new page's favicon (if any) will
|
||||
@@ -873,8 +1063,8 @@ static void on_load_changed(WebKitWebView *webview,
|
||||
uri ? uri : "(null)",
|
||||
(title && title[0]) ? title : "(none)");
|
||||
|
||||
/* Update tab title. */
|
||||
if (title && title[0]) {
|
||||
/* Update tab title (only for tabs in the main notebook). */
|
||||
if (tab != NULL && title && title[0]) {
|
||||
int index = gtk_notebook_page_num(GTK_NOTEBOOK(g_notebook),
|
||||
tab->page);
|
||||
if (index >= 0) {
|
||||
@@ -882,7 +1072,8 @@ static void on_load_changed(WebKitWebView *webview,
|
||||
}
|
||||
}
|
||||
|
||||
/* Add to history (with title for the Recents submenu tooltip). */
|
||||
/* Add to history (with title for the Recents submenu tooltip).
|
||||
* This applies to both main-window tabs and auxiliary windows. */
|
||||
if (uri != NULL && uri[0] != '\0') {
|
||||
history_add_titled(uri, (title && title[0]) ? title : NULL);
|
||||
}
|
||||
@@ -2025,6 +2216,12 @@ void tab_manager_init(GtkContainer *parent,
|
||||
|
||||
gtk_container_add(parent, g_notebook);
|
||||
|
||||
/* The main window/notebook are the default active window/notebook
|
||||
* for MCP get_active_webview(). Updated when auxiliary windows
|
||||
* gain focus (see on_window_focus_in). */
|
||||
g_active_window = window;
|
||||
g_active_notebook = g_notebook;
|
||||
|
||||
tab_manager_apply_settings();
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -11,9 +11,9 @@
|
||||
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
||||
#define SOVEREIGN_BROWSER_VERSION_H
|
||||
|
||||
#define SB_VERSION "v0.0.17"
|
||||
#define SB_VERSION "v0.0.18"
|
||||
#define SB_VERSION_MAJOR 0
|
||||
#define SB_VERSION_MINOR 0
|
||||
#define SB_VERSION_PATCH 17
|
||||
#define SB_VERSION_PATCH 18
|
||||
|
||||
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
||||
|
||||
Reference in New Issue
Block a user