Files
sovereign_browser/plans/browser-tabs.md
T

9.6 KiB
Raw Blame History

Browser Tabs Implementation Plan

Overview

Add full browser tab support to sovereign_browser. Each tab gets its own toolbar (URL bar + hamburger menu) and WebKitWebView. All tabs share a single WebKitWebContext, so the sovereign:// Nostr bridge, security settings, and TLS policy apply automatically to every tab. Tab behavior is configurable via a new settings module.

Architecture

flowchart TB
    subgraph Window
        Notebook[GtkNotebook — tab strip]
        NewTabBtn[New Tab Button +]
    end

    subgraph TabPage
        Toolbar[Toolbar: hamburger + URL entry]
        Webview[WebKitWebView]
    end

    Notebook -->|page 0| TabPage
    Notebook -->|page 1| TabPage
    Notebook -->|page N| TabPage

    TabPage -->|shared| Ctx[WebKitWebContext — sovereign bridge, security, TLS]
    Ctx --> NostrBridge[nostr_bridge.c]
    Ctx --> NostrInject[nostr_inject.c — per-webview]

    TabManager[tab_manager.c] -->|manages| Notebook
    Settings[settings.c] -->|configures| TabManager
    Session[session.c] -->|save/restore| TabManager

Key design decisions

  1. Per-tab toolbar. Each notebook page is a vertical GtkBox containing the toolbar (hamburger + URL entry) on top and the WebKitWebView below. This is simpler than a shared toolbar that has to swap signal handlers on tab switch, and it matches the user's preference.

  2. Shared WebKitWebContext. All WebKitWebView instances are created from the same context. The sovereign:// URI scheme registration, security manager flags, and TLS error policy are set once on the context and apply to all tabs. No per-tab re-registration needed.

  3. Per-webview nostr_inject_setup(). The window.nostr injection uses WebKitUserContentManager, which is per-webview. Each new tab calls nostr_inject_setup() on its own webview.

  4. tab_manager.c owns the GtkNotebook. It provides functions to create, close, switch, and query tabs. Each tab is tracked in a struct holding the webview, URL entry, hamburger button, and tab label widget.

  5. Settings module. A new settings.c/settings.h persists user preferences to ~/.sovereign_browser/settings.conf (simple key=value text file). Configurable options:

    Setting Default Description
    restore_session true Restore open tabs on next launch
    new_tab_url https://example.com URL loaded in new tabs
    tab_bar_position top top / bottom / left / right
    show_tab_close_buttons true Show per-tab close button
    middle_click_close true Middle-click on tab closes it
    ctrl_tab_switch true Ctrl+Tab cycles tabs
    max_tabs 50 Maximum simultaneous tabs
    tab_drag_reorder true Allow drag to reorder tabs
  6. Session module. session.c/session.h saves the list of open tab URLs (one per line) to ~/.sovereign_browser/session.txt on window destroy, and restores them on startup if settings.restore_session is true.

New files

src/settings.h / src/settings.c

typedef struct {
    gboolean restore_session;
    char     new_tab_url[512];
    int      tab_bar_position;   /* GTK_POS_TOP, etc. */
    gboolean show_tab_close_buttons;
    gboolean middle_click_close;
    gboolean ctrl_tab_switch;
    int      max_tabs;
    gboolean tab_drag_reorder;
} browser_settings_t;

void          settings_load(browser_settings_t *out);
void          settings_save(const browser_settings_t *s);
const browser_settings_t *settings_get(void);  /* global singleton */

src/tab_manager.h / src/tab_manager.c

typedef struct {
    WebKitWebView *webview;
    GtkWidget     *url_entry;
    GtkWidget     *hamburger;
    GtkWidget     *tab_label;     /* composite: icon + title + close btn */
    char           current_url[2048];
    char           title[256];
} tab_info_t;

void  tab_manager_init(GtkContainer *parent, WebKitWebContext *ctx);
int   tab_manager_new_tab(const char *url);
void  tab_manager_close_tab(int index);
void  tab_manager_close_active(void);
int   tab_manager_get_active_index(void);
tab_info_t *tab_manager_get_active(void);
tab_info_t *tab_manager_get(int index);
int   tab_manager_count(void);
void  tab_manager_set_title(int index, const char *title);

src/session.h / src/session.c

void session_save(void);   /* called on window destroy */
int  session_restore(void); /* called at startup, returns tab count created */

Modified files

src/main.c — major refactor

  • Remove the single webview / url_entry / toolbar creation.
  • Create WebKitWebContext once, configure security + sovereign:// bridge on it (existing code moves here, unchanged logic).
  • Call tab_manager_init() with the window's vbox and the shared context.
  • The hamburger menu builder moves into tab_manager.c (or a shared helper) since it now needs to reference the per-tab webview, not a global.
  • All menu callbacks (on_menu_reload, on_menu_stop, on_menu_inspector, on_menu_open_file, on_menu_history_item) fetch the active tab's webview via tab_manager_get_active() instead of receiving it as a parameter.
  • on_load_changed and on_load_failed are connected per-tab inside tab_manager_new_tab() and update the per-tab URL entry and tab title.
  • on_url_activate loads the URL into the active tab's webview.
  • Keyboard shortcuts: a key-press-event handler on the main window checks for Ctrl+T / Ctrl+W / Ctrl+Tab / Ctrl+Shift+Tab / Ctrl+PageUp / Ctrl+PageDown.
  • on_window_destroy calls session_save() before cleanup.

src/nostr_inject.c — no changes

nostr_inject_setup() already takes a WebKitWebView* and is called per-webview. The tab manager calls it for each new tab.

src/nostr_bridge.c — no changes

The bridge is registered on the shared WebKitWebContext. All webviews on that context automatically get the sovereign:// scheme handler.

src/history.c — no changes

History is already global. All tabs contribute to and read from the same history list.

Makefile

Add src/settings.c src/tab_manager.c src/session.c to SRC.

Tab UI layout

┌──────────────────────────────────────────────────────┐
│  [Tab 1 ×] [Tab 2 ×] [Tab 3 ×]        [+]            │  ← GtkNotebook tab strip
├──────────────────────────────────────────────────────┤
│  [☰]  https://example.com____________                │  ← per-tab toolbar
├──────────────────────────────────────────────────────┤
│                                                      │
│              WebKitWebView                           │  ← per-tab webview
│                                                      │
└──────────────────────────────────────────────────────┘

Tab label widget (composite)

Each tab label is a horizontal GtkBox:

[favicon] [Title text…] [×]
  • Favicon: placeholder icon for now (can be wired to WebKit favicon later)
  • Title: GtkLabel with ellipsize end, max width ~150px
  • Close button: GtkButton with window-close-symbolic icon, only shown if settings.show_tab_close_buttons is true

Right-click context menu on tab label

  • New Tab
  • Close Tab
  • Close Other Tabs
  • Close Tabs to the Right
  • Duplicate Tab
  • Reload Tab

Drag reordering

GtkNotebook supports drag reordering natively via gtk_notebook_set_tab_reorderable(notebook, child, TRUE). Enabled when settings.tab_drag_reorder is true.

Keyboard shortcuts

Shortcut Action
Ctrl+T New tab
Ctrl+W Close active tab
Ctrl+Tab Next tab
Ctrl+Shift+Tab Previous tab
Ctrl+PageUp Previous tab
Ctrl+PageDown Next tab
Ctrl+L Focus URL bar of active tab

Session restore

On startup, if settings.restore_session is true and ~/.sovereign_browser/session.txt exists and is non-empty, each line is loaded as a tab URL. If the file is missing or empty, a single tab is opened with the start URL (from command-line arg or settings.new_tab_url).

On window destroy, before gtk_main_quit(), the list of open tab URLs is written to ~/.sovereign_browser/session.txt.

Settings UI

A new menu item "Settings…" in the hamburger menu opens a native GTK dialog (not a web page, to keep it simple and avoid bootstrapping a sovereign://settings renderer). The dialog has checkboxes and a text entry for each configurable option. On "OK", settings_save() is called and applicable changes (like tab bar position) are applied live.

Implementation order

  1. settings.c/h — foundation, no UI yet, just load/save + global singleton
  2. tab_manager.c/h — core notebook management, per-tab page creation, tab label widget, new/close/switch
  3. session.c/h — save/restore using tab_manager API
  4. main.c refactor — wire up tab_manager, move context setup, rewire callbacks to use active tab
  5. Tab interactions — middle-click close, right-click menu, drag reorder
  6. Keyboard shortcuts — window-level key-press handler
  7. Session save/restore — integrate into startup and destroy
  8. Settings dialog — GTK dialog wired to settings module
  9. Makefile + docs — build and documentation updates