Browser git · main
Minimal browser in C (mini and big variant)
C 95.8% Markdown 3.9%git clone https://git.christianimmanuel.de/web-tools/Browser.gitwget https://git.christianimmanuel.de/web-tools/Browser/archive/Browser.tar.gzFix bug caused by loading bar
Makefile | 2 +- README.md | 28 +++++++++++++++- browser-big.c | 20 ++--------- browser_core.c | 104 ++++++++++++++++++++++++++++++++++++++++++++++++--------- browser_core.h | 1 + 5 files changed, 119 insertions(+), 36 deletions(-) diff --git a/Makefile b/Makefile index 3e16300..cf5a755 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ CC ?= gcc CFLAGS ?= -O2 -Wall -Wextra PREFIX ?= /usr/local -VERSION := 2.3.0 +VERSION := 2.3.3 SOURCES := browser_core.c browser_core.h browser-mini.c browser-big.c Makefile BUILD_ID := $(shell cat $(SOURCES) 2>/dev/null | md5sum | cut -c1-7) diff --git a/README.md b/README.md index e5fb5c1..d6b2d3b 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ browser-mini adds nothing to the core. Every hook is optional. ## Version ``` -browser-mini --version # browser-mini 2.3.0 (build bcff1fa) +browser-mini --version # browser-mini 2.3.3 (build 72335fd) make version ``` @@ -232,6 +232,32 @@ search_s = https://google.com/search?hl=en&q={} A bare `s` with nothing after it is still treated as an address, so a site whose name collides with a keyword still works. +## When a page renders blank or zero sized + +Two causes were found in the loading bar and fixed in 2.3.3. It reported a +negative minimum width, which makes GTK abandon the allocation pass and +leaves the web view unallocated — a zero-sized viewport and a white window +you can scroll forever. It also changed size on every progress tick, which +re-allocated the overlay and so re-laid-out the page dozens of times a +second: black and white flicker in time with the page's own activity. The +bar is drawn with cairo now, at a constant size, so a change repaints and +nothing more. `--no-load-bar` turns it off. + +Otherwise it is usually + +Usually a broken GL or compositing stack rather than the page. In devtools +a `body` of `0px x 0px` means the viewport itself has no size, so nothing +the page's CSS says can help. Try these in order, both browsers: + +``` +browser-mini URL --no-dmabuf +browser-mini URL --no-compositing +browser-mini URL --no-gpu +``` + +`--no-hw-decode` is the same idea for video. Any of them can go in a config +file once you know which one it was. + ## Options worth knowing | Option | Effect | diff --git a/browser-big.c b/browser-big.c index 56de3ae..285ee51 100644 --- a/browser-big.c +++ b/browser-big.c @@ -60,8 +60,7 @@ static guint g_warm_timeout_id; static gboolean g_first_load_done; /* rendering / debugging switches that only touch the environment */ -static gboolean g_no_gpu, g_no_webrtc, g_no_mediastream; -static gboolean g_no_hw_decode, g_no_dmabuf, g_no_compositing; +static gboolean g_no_webrtc, g_no_mediastream; static const char *g_gst_debug, *g_gst_dbgfile, *g_webkit_dbg; static int g_gst_level = -1; @@ -1009,10 +1008,7 @@ big_usage_options (GString *s) " --load-timeout SEC reload if a page does not commit in SEC (0 = off,\n" " default: %d)\n" " --max-reloads N auto-reloads per URL (default: %d)\n" -" --no-gpu hardware acceleration policy NEVER\n" -" --no-hw-decode WEBKIT_GST_ENABLE_HW_DECODERS=0\n" -" --no-dmabuf WEBKIT_DISABLE_DMABUF_RENDERER=1\n" -" --no-compositing WEBKIT_DISABLE_COMPOSITING_MODE=1\n" + " --no-webrtc disable WebRTC only\n" " --no-mediastream disable MediaStream / getUserMedia only\n" "\n" @@ -1084,10 +1080,6 @@ big_parse_arg (int argc, char **argv, int *i) /* ---- video / rendering ---- */ if (!strcmp (a, "--no-media-watchdog")) { g_media_watchdog = FALSE; return TRUE; } if (!strcmp (a, "--no-auto-reload")) { g_auto_reload = FALSE; return TRUE; } - if (!strcmp (a, "--no-gpu")) { g_no_gpu = TRUE; return TRUE; } - if (!strcmp (a, "--no-hw-decode")) { g_no_hw_decode = TRUE; return TRUE; } - if (!strcmp (a, "--no-dmabuf")) { g_no_dmabuf = TRUE; return TRUE; } - if (!strcmp (a, "--no-compositing")) { g_no_compositing = TRUE; return TRUE; } if (!strcmp (a, "--no-webrtc")) { g_no_webrtc = TRUE; return TRUE; } if (!strcmp (a, "--no-mediastream")) { g_no_mediastream = TRUE; return TRUE; } @@ -1165,9 +1157,6 @@ big_pre_gtk (void) } if (g_gst_dbgfile) g_setenv ("GST_DEBUG_FILE", g_gst_dbgfile, TRUE); if (g_webkit_dbg) g_setenv ("WEBKIT_DEBUG", g_webkit_dbg, TRUE); - if (g_no_hw_decode) g_setenv ("WEBKIT_GST_ENABLE_HW_DECODERS", "0", TRUE); - if (g_no_dmabuf) g_setenv ("WEBKIT_DISABLE_DMABUF_RENDERER", "1", TRUE); - if (g_no_compositing) g_setenv ("WEBKIT_DISABLE_COMPOSITING_MODE", "1", TRUE); } static void @@ -1198,11 +1187,6 @@ big_settings_ready (WebKitSettings *s) webkit_settings_set_enable_webrtc (s, FALSE); LOG ("media: WebRTC disabled\n"); } - if (g_no_gpu) { - webkit_settings_set_hardware_acceleration_policy ( - s, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER); - LOG ("gpu: hardware acceleration policy = NEVER\n"); - } } static void diff --git a/browser_core.c b/browser_core.c index fffe7a0..eaf6fd5 100644 --- a/browser_core.c +++ b/browser_core.c @@ -76,6 +76,8 @@ static gboolean g_follow_page_title = TRUE; /* see --title / --page-title */ static char *g_clip_cmd; /* --clip-cmd, NULL -> wl-copy */ static gboolean g_no_middle_paste = TRUE; /* --enable-middle-click-paste */ static gboolean g_fix_select_all = TRUE; /* see on_key */ +static gboolean g_load_bar = TRUE; /* the hairline while loading */ +static gboolean g_no_gpu, g_no_dmabuf, g_no_compositing, g_no_hw_decode; static gboolean g_want_page_title; static char *g_css_owned; /* when --css came from the config */ @@ -1126,6 +1128,7 @@ cfg_set (const char *k, const char *v) if (key_is (k, "quiet")) { g_quiet = truthy (v); return TRUE; } if (key_is (k, "page_title")) { g_want_page_title = truthy (v); return TRUE; } if (key_is (k, "select_all")) { g_fix_select_all = truthy (v); return TRUE; } + if (key_is (k, "load_bar")) { g_load_bar = truthy (v); return TRUE; } if (key_is (k, "middle_click_paste")) { g_no_middle_paste = !truthy (v); return TRUE; } if (key_is (k, "always_overwrite")) { g_always_overwrite = truthy (v); @@ -1306,6 +1309,13 @@ usage (const char *argv0, gboolean to_stdout) " --css FILE inject FILE as a user stylesheet\n" " --devtools open the inspector once the first page commits\n" " --no-media deny camera / microphone / screen-share requests\n" +" --no-load-bar do not draw the loading line at the top\n" +" --no-gpu hardware acceleration policy NEVER\n" +" --no-dmabuf WEBKIT_DISABLE_DMABUF_RENDERER=1\n" +" --no-compositing WEBKIT_DISABLE_COMPOSITING_MODE=1\n" +" --no-hw-decode WEBKIT_GST_ENABLE_HW_DECODERS=0\n" +" the four to reach for when a machine comes back up\n" +" and pages render blank or zero sized\n" " --enable-middle-click-paste\n" " let middle-button clicks reach the page. They are\n" " swallowed by default, so a stray click cannot paste\n" @@ -1371,7 +1381,8 @@ usage (const char *argv0, gboolean to_stdout) " radius border pad gap win_gap ui_scale\n" " font font_mono label_px title_px hint_px\n" " ours: title app_id zoom mod clip_cmd download_dir profile private\n" -" no_media page_title middle_click_paste select_all css\n" +" no_media page_title middle_click_paste select_all load_bar\n" +" css\n" " user_agent quiet\n" " always_overwrite\n" " search_KEY (e.g. search_s = https://google.com/search?q={})\n" @@ -1734,16 +1745,7 @@ ui_css_install (void) /* ---- the loading hairline, across the very top ---- */ g_string_append_printf (css, - "progressbar.br-load, progressbar.br-load > trough {" - " background-color: transparent; background-image: none;" - " border: none; border-radius: 0; min-height: %.0fpx;" - " padding: 0; margin: 0;" - "}" - "progressbar.br-load > trough > progress {" - " background-color: %s; border: none; border-radius: 0;" - " min-height: %.0fpx;" - "}", - 3.0 * s, c_hl, 3.0 * s); + "/* the loading hairline is drawn, not styled */"); /* ---- picker rows ---- */ g_string_append_printf (css, @@ -2560,8 +2562,17 @@ on_load_progress (GObject *obj, GParamSpec *ps, gpointer u) double p = webkit_web_view_get_estimated_load_progress (view); - gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (w->loadbar), p); - gtk_widget_set_visible (w->loadbar, p > 0.0 && p < 1.0); + /* + * Only the fill inside the bar changes. Nothing here may alter the + * bar's own size: the previous version resized it on every tick, + * which re-allocated the whole overlay - and re-allocating a + * WebKitWebView makes the web process re-lay-out and recomposite. + * At the rate estimated-load-progress fires, that is the black and + * white flicker while a page loads. + */ + w->load_frac = CLAMP (p, 0.0, 1.0); + gtk_widget_set_visible (w->loadbar, g_load_bar && p > 0.0 && p < 1.0); + gtk_widget_queue_draw (w->loadbar); /* repaint, not re-layout */ } static void @@ -4453,6 +4464,27 @@ on_middle_press (GtkGestureClick *g, int n, double x, double y, gpointer u) * or shorter than they would like: everything is clamped to what the * window actually has, and the popup is centered in whatever is left. */ +/* + * Drawn by hand. A GtkProgressBar kept reporting a negative minimum width + * for its inner node once its padding and height were overridden, and + * anything that changes size on every progress tick re-allocates the + * overlay - which re-lays-out the page. This changes pixels only. + */ +static void +draw_loadbar (GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer u) +{ + (void) area; + Win *w = u; + Color c = g_theme.hl; + + if (w->load_frac <= 0.0) + return; + + cairo_set_source_rgba (cr, c.r, c.g, c.b, c.a); + cairo_rectangle (cr, 0, 0, width * CLAMP (w->load_frac, 0.0, 1.0), height); + cairo_fill (cr); +} + static gboolean on_overlay_position (GtkOverlay *ov, GtkWidget *child, GdkRectangle *alloc, gpointer u) { @@ -4468,6 +4500,22 @@ on_overlay_position (GtkOverlay *ov, GtkWidget *child, GdkRectangle *alloc, gpoi /* One width for every panel, whatever it holds: the address popup, * the history, the directory popup and the key list are all the same * object as far as the eye is concerned. */ + /* + * The bar is a plain box laid out here rather than a GtkProgressBar: + * a progress bar with its padding and min-height overridden computed a + * negative min width for its inner node, and GTK abandons an + * allocation pass that sees one - which left the page itself + * unallocated, a zero-sized viewport and a white window. + */ + if (child == w->loadbar) { + alloc->x = 0; + alloc->y = 0; + alloc->width = W; /* constant, so the page is never + * re-allocated as the bar fills */ + alloc->height = MAX (1, (int) (3 * g_theme.ui_scale)); + return TRUE; + } + if (child == w->omni || child == w->keys) want = (int) (g_theme.popup_w * g_theme.ui_scale); else if (child == w->topright) @@ -4650,7 +4698,8 @@ window_new (WebKitWebView *view, gboolean primary) gtk_overlay_add_overlay (GTK_OVERLAY (overlay), w->urltoast); /* a hairline across the very top, so a slow page still says something */ - w->loadbar = gtk_progress_bar_new (); + w->loadbar = gtk_drawing_area_new (); + gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (w->loadbar), draw_loadbar, w, NULL); gtk_widget_add_css_class (w->loadbar, "br-load"); gtk_widget_set_halign (w->loadbar, GTK_ALIGN_FILL); gtk_widget_set_valign (w->loadbar, GTK_ALIGN_START); @@ -4903,6 +4952,12 @@ setup_settings (void) webkit_settings_set_javascript_can_access_clipboard (g_settings, TRUE); webkit_settings_set_enable_write_console_messages_to_stdout (g_settings, !g_quiet); + if (g_no_gpu) { + webkit_settings_set_hardware_acceleration_policy ( + g_settings, WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER); + LOG ("gpu: hardware acceleration policy = NEVER\n"); + } + if (g_app->settings_ready) g_app->settings_ready (g_settings); } @@ -4937,8 +4992,9 @@ start_page_html (void) char *html = g_strdup_printf ( "<!doctype html><meta charset=\"utf-8\"><title>%s</title><style>" -"html,body{height:100%%;margin:0}" -"body{display:flex;align-items:center;justify-content:center;" +"html,body{margin:0}" +"body{min-height:100vh;box-sizing:border-box;padding:8vh 2rem;" +"display:flex;align-items:center;justify-content:center;" "background:radial-gradient(circle at 50%% 38%%,%s 0%%,%s 72%%);" "font-family:%s,sans-serif;color:%s;" "-webkit-font-smoothing:antialiased}" @@ -5060,6 +5116,16 @@ browser_main (int argc, char **argv, const BrowserApp *app) open_devtools = TRUE; } else if (!strcmp (a, "--no-media")) { g_deny_media = TRUE; + } else if (!strcmp (a, "--no-load-bar")) { + g_load_bar = FALSE; + } else if (!strcmp (a, "--no-gpu")) { + g_no_gpu = TRUE; + } else if (!strcmp (a, "--no-dmabuf")) { + g_no_dmabuf = TRUE; + } else if (!strcmp (a, "--no-compositing")) { + g_no_compositing = TRUE; + } else if (!strcmp (a, "--no-hw-decode")) { + g_no_hw_decode = TRUE; } else if (!strcmp (a, "--enable-middle-click-paste")) { g_no_middle_paste = FALSE; } else if (!strcmp (a, "--download-dir")) { @@ -5132,6 +5198,12 @@ browser_main (int argc, char **argv, const BrowserApp *app) if (g_private && (g_profile || g_clear_data)) LOG ("note: --private ignores --profile/--clear-data\n"); + /* Must be set before any process is spawned, so the web and GPU + * processes inherit them. */ + if (g_no_dmabuf) g_setenv ("WEBKIT_DISABLE_DMABUF_RENDERER", "1", TRUE); + if (g_no_compositing) g_setenv ("WEBKIT_DISABLE_COMPOSITING_MODE", "1", TRUE); + if (g_no_hw_decode) g_setenv ("WEBKIT_GST_ENABLE_HW_DECODERS", "0", TRUE); + if (app->pre_gtk) app->pre_gtk (); diff --git a/browser_core.h b/browser_core.h index f955afc..7c900fe 100644 --- a/browser_core.h +++ b/browser_core.h @@ -46,6 +46,7 @@ typedef struct { GtkWidget *omnicancel; GtkWidget *urltoast; /* the address, top left, on a new page */ GtkWidget *loadbar; /* a thin line across the top while loading */ + double load_frac; guint urltoast_id; GtkWidget *keys; /* key reference, hidden by default */ GtkWidget *topright; /* box holding the toast and the downloads */