Skip to content

Conversation

@jiripolasek
Copy link
Collaborator

Summary of the Pull Request

This PR introduces a new method for determining the default browser using the Windows Shell API. The new provider selects the browser associated with the HTTPS protocol (falling back to HTTP if necessary). The original implementation is retained as a fallback for now, and the codebase is prepared for future extensions (e.g., manual default-browser selection).

As a flyby, it also fixes an issue where commands continued showing the previous browser name if the user changed their default browser while the Command Palette was running.

One-liner for change log

Fixed default browser selection in the Web Search built-in extension.

PR Checklist

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

@github-actions

This comment has been minimized.

@github-actions
Copy link

github-actions bot commented Nov 6, 2025

@check-spelling-bot Report

🔴 Please review

See the 📂 files view, the 📜action log, or 📝 job summary for details.

❌ Errors, Warnings, and Notices Count
ℹ️ candidate-pattern 1
❌ forbidden-pattern 1
⚠️ ignored-expect-variant 4

See ❌ Event descriptions for more information.

These words are not needed and should be removed cabstr CIBUILD djwsxzxb icf installscopeperuser ksa Olllama rap registryroot regroot rtm suntimes TARGETDIR utm

Pattern suggestions ✂️ (1)

You could add these patterns to .github/actions/spell-check/patterns.txt:

# Automatically suggested patterns

# hit-count: 1 file-count: 1
# Amazon
\bamazon\.com/[-\w]+/(?:dp/[0-9A-Z]+|)

Alternatively, if a pattern suggestion doesn't make sense for this project, add a #
to the beginning of the line in the candidates file with the pattern to stop suggesting it.

If the flagged items are 🤯 false positives

If items relate to a ...

  • binary file (or some other file you wouldn't want to check at all).

    Please add a file path to the excludes.txt file matching the containing file.

    File paths are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your files.

    ^ refers to the file's path from the root of the repository, so ^README\.md$ would exclude README.md (on whichever branch you're using).

  • well-formed pattern.

    If you can write a pattern that would match it,
    try adding it to the patterns.txt file.

    Patterns are Perl 5 Regular Expressions - you can test yours before committing to verify it will match your lines.

    Note that patterns can't match multiline strings.

@michaeljolley michaeljolley added the Product-Command Palette Refers to the Command Palette utility label Nov 6, 2025
Copilot finished reviewing on behalf of michaeljolley November 15, 2025 00:01
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the default browser detection mechanism in the Command Palette's Web Search extension to use the Windows Shell API (AssocQueryStringW) as the primary method, with fallbacks to legacy registry access and Microsoft Edge. It introduces a service-based architecture with IBrowserInfoService and multiple provider implementations. The PR also fixes an issue where the browser name in the UI would not update when users changed their default browser while Command Palette was running.

Key Changes

  • Introduced IBrowserInfoService interface and DefaultBrowserInfoService implementation with provider chain pattern
  • Added ShellAssociationProvider using Windows Shell API for protocol associations (https/http)
  • Retained legacy registry-based detection as LegacyRegistryAssociationProvider for fallback compatibility
  • Updated all command items and pages to use the new browser info service for dynamic browser name retrieval

Reviewed Changes

Copilot reviewed 24 out of 25 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
Helpers/Browser/DefaultBrowserInfoService.cs Core service orchestrating browser detection with caching and provider fallback chain
Helpers/Browser/Providers/ShellAssociationProvider.cs Primary provider using AssocQueryStringW to query https/http protocol associations
Helpers/Browser/Providers/LegacyRegistryAssociationProvider.cs Fallback provider using registry access for older browser detection method
Helpers/Browser/Providers/AssociationProviderBase.cs Base class providing shared command parsing and indirect string resolution logic
Helpers/Browser/Providers/FallbackMsEdgeBrowserProvider.cs Final fallback returning hardcoded Microsoft Edge path
Helpers/NativeMethods.cs P/Invoke declarations for AssocQueryStringW shell API
Helpers/Browser/BrowserInfo.cs Record type containing browser path, name, and arguments pattern
Helpers/Browser/IBrowserInfoService.cs Service interface for retrieving default browser information
Helpers/Browser/BrowserInfoServiceExtensions.cs Extension method for opening URLs in the default browser
Commands/SearchWebCommand.cs Updated to use IBrowserInfoService instead of static DefaultBrowserInfo
Commands/OpenURLCommand.cs Updated to use IBrowserInfoService for opening URLs
Pages/WebSearchListPage.cs Updated to dynamically retrieve browser name via service
FallbackExecuteSearchItem.cs Updated to get browser name dynamically on each query update
FallbackOpenURLItem.cs Updated to get browser name dynamically and changed IsValidUrl to private
WebSearchTopLevelCommandItem.cs Constructor now accepts IBrowserInfoService parameter
WebSearchCommandsProvider.cs Creates shared DefaultBrowserInfoService instance and passes to all commands
Properties/Resources.resx Added "default browser" localization resource as fallback display text
Tests/.../MockBrowserInfoService.cs Mock implementation returning fixed browser info for unit tests
Tests/.../SettingsManagerTests.cs Updated test to pass mock browser service
Tests/.../QueryTests.cs Updated tests to pass mock browser service
Files not reviewed (1)
  • src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WebSearch/Properties/Resources.Designer.cs: Language not supported

/// <summary>
/// Gets information about the system's default web browser.
/// </summary>
/// <returns></returns>
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Incomplete XML documentation: The <returns> element is empty and should describe what the method returns.

Should be:

/// <returns>A <see cref="BrowserInfo"/> object containing information about the default browser, or null if the browser information cannot be determined.</returns>
Suggested change
/// <returns></returns>
/// <returns>A <see cref="BrowserInfo"/> object containing information about the default browser, or null if the browser information cannot be determined.</returns>

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +12

public record BrowserInfo
{
public required string Path { get; init; }

public required string Name { get; init; }

Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Missing XML documentation for public type: The BrowserInfo record is public but lacks XML documentation. All public types, properties, and members should be documented with XML comments explaining their purpose.

Add documentation like:

/// <summary>
/// Contains information about a web browser installation.
/// </summary>
public record BrowserInfo
{
    /// <summary>
    /// Gets the file path to the browser executable or a packaged application URI.
    /// </summary>
    public required string Path { get; init; }

    /// <summary>
    /// Gets the user-friendly name of the browser.
    /// </summary>
    public required string Name { get; init; }

    /// <summary>
    /// Gets the command line arguments pattern for launching URLs, or null if not applicable.
    /// </summary>
    public string? ArgumentsPattern { get; init; }
}
Suggested change
public record BrowserInfo
{
public required string Path { get; init; }
public required string Name { get; init; }
/// <summary>
/// Contains information about a web browser installation.
/// </summary>
public record BrowserInfo
{
/// <summary>
/// Gets the file path to the browser executable or a packaged application URI.
/// </summary>
public required string Path { get; init; }
/// <summary>
/// Gets the user-friendly name of the browser.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// Gets the command line arguments pattern for launching URLs, or null if not applicable.
/// </summary>

Copilot uses AI. Check for mistakes.
"ProgId")
?? GetRegistryValue(
@"HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice",
"ProgId");
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Potential null reference exception: On lines 23-24, progId could be null if both registry lookups fail (lines 17-22), but it's used in string interpolation without null checking. This will cause GetRegistryValue to be called with paths containing "null" as a string literal.

Add a null check:

var progId = GetRegistryValue(/*...*/)
             ?? GetRegistryValue(/*...*/);

if (string.IsNullOrEmpty(progId))
{
    return null;
}

var appName = GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}\Application", "ApplicationName")
              ?? GetRegistryValue($@"HKEY_CLASSES_ROOT\{progId}", "FriendlyTypeName");
Suggested change
"ProgId");
"ProgId");
if (string.IsNullOrEmpty(progId))
{
return null;
}

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +49
var span = cch <= 512 ? stackalloc char[(int)cch] : new char[(int)cch];

fixed (char* p = span)
{
var hr = NativeMethods.AssocQueryStringW(NativeMethods.AssocF.IsProtocol, what, protocol, null, p, ref cch);
if (hr != 0 || cch == 0)
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Potential buffer overflow risk with stackalloc: On line 44, when cch > 512, a heap-allocated array is created, but when cch <= 512, stackalloc is used. However, the second call to AssocQueryStringW on line 48 might update cch to a larger value than initially reported, potentially overflowing the stack-allocated buffer.

The safer approach is to check cch hasn't changed after the second call:

var originalCch = cch;
var span = cch <= 512 ? stackalloc char[(int)cch] : new char[(int)cch];

fixed (char* p = span)
{
    var hr = NativeMethods.AssocQueryStringW(NativeMethods.AssocF.IsProtocol, what, protocol, null, p, ref cch);
    if (hr != 0 || cch == 0 || cch > originalCch)
    {
        return null;
    }
    // ...
}
Suggested change
var span = cch <= 512 ? stackalloc char[(int)cch] : new char[(int)cch];
fixed (char* p = span)
{
var hr = NativeMethods.AssocQueryStringW(NativeMethods.AssocF.IsProtocol, what, protocol, null, p, ref cch);
if (hr != 0 || cch == 0)
var originalCch = cch;
var span = cch <= 512 ? stackalloc char[(int)cch] : new char[(int)cch];
fixed (char* p = span)
{
var hr = NativeMethods.AssocQueryStringW(NativeMethods.AssocF.IsProtocol, what, protocol, null, p, ref cch);
if (hr != 0 || cch == 0 || cch > originalCch)

Copilot uses AI. Check for mistakes.
private readonly IBrowserInfoService _browserInfoService;

public string Arguments { get; internal set; } = string.Empty;
public string Arguments { get; internal set; }
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Default initialization removed for Arguments property: The Arguments property no longer has a default value of string.Empty. This could lead to a null reference exception if the property is accessed before being set, even though the constructor does set it.

Consider adding the default initializer back for defensive programming:

public string Arguments { get; internal set; } = string.Empty;
Suggested change
public string Arguments { get; internal set; }
public string Arguments { get; internal set; } = string.Empty;

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +62
_lastUpdateTickCount = curTickCount;
lock (_updateLock)
{
Copy link

Copilot AI Nov 15, 2025

Choose a reason for hiding this comment

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

Race condition in UpdateIfTimePassed: There's a potential race condition where multiple threads could pass the timeout check (line 55) simultaneously before any of them updates _lastUpdateTickCount (line 60). This means multiple threads could enter the lock and call UpdateCore() unnecessarily.

Consider moving the timestamp update inside the lock:

public void UpdateIfTimePassed()
{
    var curTickCount = Environment.TickCount64;
    if (curTickCount - _lastUpdateTickCount < UpdateTimeout && _defaultBrowser != null)
    {
        return;
    }

    lock (_updateLock)
    {
        // Double-check after acquiring lock
        if (curTickCount - _lastUpdateTickCount < UpdateTimeout && _defaultBrowser != null)
        {
            return;
        }
        _lastUpdateTickCount = curTickCount;
        _defaultBrowser = UpdateCore();
    }
}
```suggestion
        lock (_updateLock)
        {
            // Double-check after acquiring lock
            if (curTickCount - _lastUpdateTickCount < UpdateTimeout && _defaultBrowser != null)
            {
                return;
            }
            _lastUpdateTickCount = curTickCount;

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Command Palette Refers to the Command Palette utility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Command Palette Web Search and Open URL do not respect default browser, use Microsoft Edge instead

2 participants