← All posts

Reading the browser address bar with UI Automation in .NET

FocusLock keeps a study session on learning sites. The obvious way to do that is a browser extension, but on a managed work laptop extensions are often locked down by policy. So I went another way: read the active tab's address from outside the browser, with Windows UI Automation (UIA), the same API screen readers use.

Finding the address bar

Chromium-based browsers (Edge, Chrome, Brave, Vivaldi, Opera) all expose the address bar as a UIA element with the class name OmniboxViewViews. It supports the ValuePattern, so reading it takes a few lines:

csharp
using System.Windows.Automation;

static string? ReadAddress(IntPtr browserWindow)
{
    var window = AutomationElement.FromHandle(browserWindow);
    var omnibox = window.FindFirst(
        TreeScope.Descendants,
        new PropertyCondition(AutomationElement.ClassNameProperty, "OmniboxViewViews"));

    if (omnibox is null || !omnibox.TryGetCurrentPattern(ValuePattern.Pattern, out var pattern))
        return null;

    return ((ValuePattern)pattern).Current.Value;
}

FindFirst with TreeScope.Descendants walks a large tree, so cache the element per window and only search again when it goes stale.

Surprise 1: every browser shows the address differently

Browser Shown in the address bar
Edge https://learn.microsoft.com/...
Chrome learn.microsoft.com/... (no scheme)
Edge, local file C:/Users/...

Normalize before you compare anything: add https:// when there's no scheme, and treat drive-letter paths as local files.

Surprise 2: focus events arrive late

My first version listened for UIA focus-changed events to know when the learner was typing a new address. Under load, those events arrived seconds late, after the page had already loaded. Polling the focused element every 50 ms turned out to be both simpler and more reliable.

Lesson: UIA events are great for "eventually", not for "right now".

Surprise 3: tabs have stable IDs

Each tab in the tab strip (EdgeTabContainerImpl in Edge, TabContainerImpl in Chrome) is a UIA TabItem with a runtime ID that stays the same while the tab lives. That made per-tab rules possible, like trusting a lab page that was opened from a Microsoft Learn module.

Takeaways

  • UI Automation can read browser state without an extension, and it works the same across Chromium browsers.
  • Normalize addresses before comparing them.
  • Prefer short polling over UIA events when timing matters.
  • Fail safe: if the address bar can't be read, allow rather than block, and log it.