Skip to content

LibWebView+UI: Add browsing history and rich address bar autocomplete#8933

Draft
awesomekling wants to merge 10 commits intoLadybirdBrowser:masterfrom
awesomekling:hizztory
Draft

LibWebView+UI: Add browsing history and rich address bar autocomplete#8933
awesomekling wants to merge 10 commits intoLadybirdBrowser:masterfrom
awesomekling:hizztory

Conversation

@awesomekling
Copy link
Copy Markdown
Member

This branch adds a SQLite-backed HistoryStore in LibWebView that persists visited pages along with their title, favicon, visit count, and last-visit time. WebContentClient now records navigations, title changes, and favicon updates into the store, and a "Clear browsing history" action is wired up in the Privacy settings page.

The address bar autocomplete pipeline has been reworked so that suggestions are structured rows, history hits, a "Search with" entry, and plain completions, each carrying a title, URL, and subtitle rather than a flat string.

Both the Qt and AppKit UIs render the new rich rows with favicons and subtitles.

This is all pretty simple and barebones! There's plenty more data we could track and plenty more UI we could build on top of it, but we gotta start somewhere. :)

HF0z3h1XwAAb0GF Screenshot from 2026-04-16 00-45-44

@awesomekling awesomekling force-pushed the hizztory branch 2 times, most recently from 98775f7 to 591d9dd Compare April 15, 2026 23:04
Copy link
Copy Markdown
Contributor

@trflynn89 trflynn89 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super cool :)

I went through this a bit quickly. Some of it kinda hard to review where things are added to the history store in one commit and then removed or reshaped in the next commit, but 🤷‍♂️

Thinking out loud, I'm wondering if our monolith Ladybird.db makes sense long term, or if we want to split it out into Cookies.db, History.db, etc.? (This can be decided after merging)

Comment thread Libraries/LibWebView/HistoryStore.cpp Outdated
Comment thread Libraries/LibWebView/HistoryStore.cpp Outdated
Comment thread Libraries/LibWebView/HistoryStore.h
Comment thread Libraries/LibWebView/HistoryDebug.h Outdated
Comment thread Libraries/LibWebView/HistoryStore.cpp Outdated
Comment thread UI/AppKit/Interface/Autocomplete.mm Outdated
Comment thread Libraries/LibWebView/Autocomplete.cpp Outdated
Comment thread Libraries/LibWebView/URL.cpp
Comment thread Libraries/LibWebView/URL.h Outdated
Comment thread UI/Icons/search.tvgt Outdated
@jdahlin
Copy link
Copy Markdown
Contributor

jdahlin commented Apr 16, 2026

A couple of things related SQLite and comparison with other browsers:

  1. Favicon storage: Favicons are base64 TEXT inline in the History table. Firefox/Chrome store BLOBs in a separate database. Alternatively, don't SELECT favicon in the search query, instead fetch lazily after ranking.
  2. Autocomplete full table scan: search_entries computes searchable_url via nested SUBSTR/INSTR/LIKE per row. No index can help here, instead a stored+indexed searchable_url column would turn this into an index range scan.
  3. No frecency: Ranking by visit_count DESC means a page visited 100 times six months ago beats one visited 3 times today. Firefox/Chrome use a recency-weighted score as the primary sort key.
  4. Per-statement auto-commit: A page load triggers up to 3 separate fsyncs (record_visit, update_title, update_favicon). Wrapping per-navigation writes in a single BEGIN/COMMIT would avoid that.

Not suggesting all of these should be fixed in this PR, but the current implementation will be too slow at some point when the history grows.

@awesomekling awesomekling marked this pull request as draft April 16, 2026 09:57
Add a HistoryStore abstraction with transient and persisted backends,
normalize recorded URLs, and skip non-browsable schemes.

Cover lookup and persistence in TestHistoryStore so history-driven
features can share one backend.
Teach LibWebView autocomplete to query HistoryStore before falling back
to remote engines and move the wiring out of the AppKit frontend.
Refine matching so scheme and www. boilerplate do not dominate results,
short title and substring queries stay quiet, and history tracing can
explain what the ranking code is doing.
Record visits as soon as a page produces useful metadata such as a
title or favicon so pages that never finish loading still become
autocomplete candidates.

Store favicons in the history schema from the start instead of
introducing an upgrade path inside this series, and cover persisted
metadata behavior in TestHistoryStore.
Replace the frontend-facing Vector<String> flow with structured
AutocompleteSuggestion objects carrying source, section, title,
and favicon metadata.

Build merged history and literal-URL rows in LibWebView, deduplicate
equivalent URL suggestions, move the autocomplete URL helpers out
of URL.h, and update the history and URL tests around the new model.
Replace the AppKit popover because it becomes key and steals focus from
the location field while suggestions are visible. Use a borderless
child window anchored to the toolbar item instead, and dismiss it on
resize or focus changes so the browser chrome stays predictable.
Render AutocompleteSuggestion rows with section headers, favicons,
titles, and secondary text in the child-window popup instead of just
plain strings.

Move the AppKit popup and inline completion onto the shared suggestion
model, and share the base64 PNG decoding helper with the application
menu icon loading path.
Route the existing Clear Browsing Data dialog through HistoryStore's
time-range deletion path as well. That makes the Settings action
remove visited pages from persisted history and from history-backed
address bar suggestions instead of only touching cache and site data.

Add a history checkbox to the dialog, thread its state through the
Settings WebUI message, and cover remove_entries_accessed_since() for
both transient and persisted stores in TestHistoryStore.
Some navigations report the same URL more than once through
WebContentClient::did_change_url(). Forwarding those duplicate updates
into the frontend turned an internal no-op into visible UI churn,
including location bar resets and AppKit focus stealing.

Treat repeated URL notifications as a no-op inside LibWebView so
frontends only react to real URL changes.
Replace the old QCompleter-based popup with a custom list that renders
AutocompleteSuggestion rows with titles/URLs/section headers/favicons.

Parent the popup inside the window so it does not steal focus, route
keyboard and mouse activation through WebView::Autocomplete, and add
inline completion with backspace suppression and top-row selection.
Synthesize an extra AutocompleteSuggestion at the top of the Search
Suggestions section whenever there is a configured search engine and
the typed query is not URL-shaped.

Use the query as the row's primary text, carry a "Search with <engine>"
subtitle, and render that subtitle in the AppKit and Qt popups so the
explicit search fallback stays visible and readable even when history
fills the list.
@awesomekling
Copy link
Copy Markdown
Member Author

Thinking out loud, I'm wondering if our monolith Ladybird.db makes sense long term, or if we want to split it out into Cookies.db, History.db, etc.? (This can be decided after merging)

Moved this to History.db right away. It just makes sense :)

@awesomekling
Copy link
Copy Markdown
Member Author

A couple of things related SQLite and comparison with other browsers:

1. Favicon storage: Favicons are base64 TEXT inline in the History table. Firefox/Chrome store BLOBs in a separate database. Alternatively, don't SELECT favicon in the search query, instead fetch lazily after ranking.

2. Autocomplete full table scan: search_entries computes searchable_url via nested SUBSTR/INSTR/LIKE per row. No index can help here, instead a stored+indexed searchable_url column would turn this into an index range scan.

3. No frecency: Ranking by visit_count DESC means a page visited 100 times six months ago beats one visited 3 times today. Firefox/Chrome use a recency-weighted score as the primary sort key.

4. Per-statement auto-commit: A page load triggers up to 3 separate fsyncs (record_visit, update_title, update_favicon). Wrapping per-navigation writes in a single BEGIN/COMMIT would avoid that.

Not suggesting all of these should be fixed in this PR, but the current implementation will be too slow at some point when the history grows.

All good points that we can and should address! ... but let's do it separately 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants