Skip to content

Browser opening could be faster on Windows by avoiding wslview #10466

Closed as not planned

Description

@williammartin

Description

Extracted from cli/browser#14


After noticing that wslview felt really slow, I looked into the source code to see what the cause was. I noticed that wslview was doing a lot of stuff which, IMO, is unnecessary for the purposes of gh.

What wslview does is:

  • Queries the Windows Registry via reg.exe to check if the build number is high enough (not used when opening URLs)
  • Validates the URL by curling it and asserting that content was returned (a potential source of slowness, this can be skipped with --skip-validation-check on newer versions of wslview).

And, after these validations, what it boils down to is calling either /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "explorer.exe $URL" or /mnt/c/Windows/System32/cmd.exe /c explorer.exe "$URL". Starting a PowerShell process seems to contribute a lot to the slowness.

There's a bit of extra work in case a different prefix is used than /mnt or Windows is installed to a different drive than C:, but this should sum up the vast majority of cases.

I think these validations aren't necessary as long as LookPath is used, and explorer.exe can be called directly. I've noticed a pretty decent gain in speed when using GH_BROWSER=explorer.exe over GH_BROWSER=wslview.


Activity

  1. williammartin commented on Feb 19, 2025

    @williammartin
    MemberAuthor

    Acceptance Criteria

    Given I am on Windows
    When I invoke a command that opens the browser
    Then explorer.exe is used if it is on the PATH

    Notes

    It may not be possible to validate the A/C in a blackbox manner because the program used is not made visible anywhere. Perhaps windows has the equivalent of strace, or perhaps we can use a timer program to see that the speed has improved.

  2. williammartin commented on Feb 19, 2025

    @williammartin
    MemberAuthor

    @spenserblack any thoughts on how I might blackbox validate this? I don't know much about powershell / wsl.

  3. williammartin commented on Feb 19, 2025

    @williammartin
    MemberAuthor

    Using hyperfine:

    will@WILLIAMMART8251:~/workspace/cli$ hyperfine --warmup 1 'GH_BROWSER=wslview gh repo view --web'
    Benchmark 1: GH_BROWSER=wslview gh repo view --web
      Time (mean ± σ):      1.862 s ±  0.190 s    [User: 0.101 s, System: 0.292 s]
      Range (min … max):    1.742 s …  2.365 s    10 runs
    
    will@WILLIAMMART8251:~/workspace/cli$ hyperfine --ignore-failure --warmup 1 'GH_BROWSER=explorer.exe gh repo view --web'
    Benchmark 1: GH_BROWSER=explorer.exe gh repo view --web
      Time (mean ± σ):     683.1 ms ± 112.0 ms    [User: 46.9 ms, System: 51.6 ms]
      Range (min … max):   600.5 ms … 965.6 ms    10 runs
    
      Warning: Ignoring non-zero exit code.
    

    We can see that using explorer.exe is much faster (though it was exiting with code 1 which I had to ignore even though the webpage was opening). @spenserblack did you do anything different than setting GH_BROWSER=explorer.exe when testing this out?

    will@WILLIAMMART8251:~/workspace/cli$ GH_BROWSER=explorer.exe gh repo view --web
    Opening github.com/cli/cli in your browser.
    exit status 1
    
  4. spenserblack commented on Feb 19, 2025

    @spenserblack
    Contributor

    @spenserblack did you do anything different than setting GH_BROWSER=explorer.exe when testing this out?

    I had only done GH_BROWSER=explorer.exe. I didn't notice that explorer.exe would return status 1, but now I see that that's indeed the case. Seems to be an issue with running explorer.exe in WSL: microsoft/WSL#6565. It also doesn't seem to be limited to URLs -- explorer.exe . also returns 1 (but succeeds in opening the current directory). Either my Googling skills are bad, or it's really hard to find any documentation about explorer.exe and why it would return an error code here 😅

  5. spenserblack commented on Feb 19, 2025

    @spenserblack
    Contributor

    Just informally testing by running commands, I see that cmd.exe /c start URL has roughly similar performance gains. Perhaps gh-browser can be refactored so that providers can be passed additional arguments?

    wslview can use cmd.exe over powershell.exe, which should be faster AFAIK, by setting WSLVIEW_DEFAULT_ENGINE. But it seems like it's not available on my device. On Ubuntu 24, apt lists wslu 3.2.3, and the default engine override isn't available until 4.0.0.

  6. williammartin commented on Feb 19, 2025

    @williammartin
    MemberAuthor

    If you want to try a map[string][]string where the []string contains extra args we could give that a go I guess? But maybe we should use --skip-validation-check then instead of cmd.exe? Do you have a breakdown of the time spent in the registry vs the lookup? Suspect the latter is the majority?

  7. spenserblack commented on Feb 19, 2025

    @spenserblack
    Contributor

    I can try to break it down later, but I'm fairly certain that most of the slowdown occurs when wslview, by default, starts a powershell.exe process. For whatever reason, powershell.exe seems to typically take a while to start up.

    I actually had to deal with this in another project where I was trying to query the registry. At first I used a powershell.exe subprocess since PowerShell had some useful cmdlets. But I found a direct call to reg.exe was much faster. I concluded that calling powershell.exe as a subprocess should be avoided if there's an alternative.

    So in summary, I think the real issue is that powershell.exe gets called. But I'm not too fond of the registry query or redundant HTTP call, either 😅

  8. williammartin commented on Feb 19, 2025

    @williammartin
    MemberAuthor

    Sure, well let's give it a go with cmd.exe /c start with the same map[string][string] idea.

  9. andyfeller commented on Feb 19, 2025

    @andyfeller
    Contributor

    @williammartin @spenserblack : catching up on cli/browser#14 and the commentary here, is there a reasonable way that we can detect and handle WSL Linux situations more specifically?

    Looking at the draft PR above, the non-exported openBrowser logic is taking advantage of Go build constraints to differentiate linux from windows, which is a gray area for WSL. I ask to see if there is some other way to detect these scenarios if possible.

    From microsoft/WSL#423, it seems like there checking for /proc/version is semi-official way from the WSL perspective. I don't think there is anything in Go runtime to distinguish WSL. 🤔

  10. andyfeller commented on Feb 19, 2025

    @andyfeller
    Contributor

    Acceptance Criteria

    Given I am on Windows When I invoke a command that opens the browser Then explorer.exe is used if it is on the PATH

    Notes

    It may not be possible to validate the A/C in a blackbox manner because the program used is not made visible anywhere. Perhaps windows has the equivalent of strace, or perhaps we can use a timer program to see that the speed has improved.

    Putting a finer point on this, here are all the commands with --web flag that will be affected:

    1. gh auth login [flags]
    2. gh codespace code [flags]
    3. gh codespace create [flags]
    4. gh codespace list [flags]
    5. gh extension search [<query>] [flags]
    6. gh gist create [<filename>... | -] [flags]
    7. gh gist view [<id> | <url>] [flags]
    8. gh issue comment {<number> | <url>} [flags]
    9. gh issue create [flags]
    10. gh issue list [flags]
    11. gh issue view {<number> | <url>} [flags]
    12. gh label list [flags]
    13. gh pr checks [<number> | <url> | <branch>] [flags]
    14. gh pr comment [<number> | <url> | <branch>] [flags]
    15. gh pr create [flags]
    16. gh pr diff [<number> | <url> | <branch>] [flags]
    17. gh pr list [flags]
    18. gh pr view [<number> | <url> | <branch>] [flags]
    19. gh project list [flags]
    20. gh project view [<number>] [flags]
    21. gh release view [<tag>] [flags]
    22. gh repo autolink list [flags]
    23. gh repo license view {<license-key> | <SPDX-ID>} [flags]
    24. gh repo view [<repository>] [flags]
    25. gh ruleset check [<branch>] [flags]
    26. gh ruleset list [flags]
    27. gh ruleset view [<ruleset-id>] [flags]
    28. gh run view [<run-id>] [flags]
    29. gh search code <query> [flags]
    30. gh search commits [<query>] [flags]
    31. gh search issues [<query>] [flags]
    32. gh search prs [<query>] [flags]
    33. gh search repos [<query>] [flags]
    34. gh workflow view [<workflow-id> | <workflow-name> | <filename>] [flags]
  11. spenserblack commented on Feb 19, 2025

    @spenserblack
    Contributor

    is there a reasonable way that we can detect and handle WSL Linux situations more specifically?

    I believe that one of the best ways (in terms of both reliability and simplicity) is to check for the presence of one of the WSL_* environment variables. WSL_DISTRO_NAME, which reports the Linux distribution name, should be a good candidate, but I'll investigate a bit more.

    Regarding the build constraints, I'd say that the logic should fall under Linux, since the user will most likely be executing a Linux binary of gh from within WSL.

    I suppose they could execute gh.exe from within the subsystem. I've never tried that out, but I'd be curious if gh.exe is even usable in the subsystem. For example, the resolution to the absolute path is different in the subsystem, so it might fail to read config files. So IMO executing the Windows binary, gh.exe, from within the Linux subsystem is a very unusual edge-case that might not need to be handled.

  12. williammartin commented on Feb 20, 2025

    @williammartin
    MemberAuthor

    On reflection, I'm a bit concerned about cmd.exe /c start opening up some avenues for command injection, in that openBrowser takes a bare string. Consumers shouldn't be passing arbitrary arguments to openBrowser but people do what people do. Not sure if / how to mitigate that.

    is there a reasonable way that we can detect and handle WSL Linux situations more specifically?

    Is there some advantage you can see by doing that? I can't think of a way in which the behaviour would change given this knowledge.,

    Regarding the build constraints, I'd say that the logic should fall under Linux, since the user will most likely be executing a Linux binary of gh from within WSL.

    That's currently what happens with the logic living under _linux.go.

  13. spenserblack commented on Feb 20, 2025

    @spenserblack
    Contributor

    On reflection, I'm a bit concerned about cmd.exe /c start opening up some avenues for command injection, in that openBrowser takes a bare string. Consumers shouldn't be passing arbitrary arguments to openBrowser but people do what people do.

    Thanks for bringing up security -- I was so focused on speed that I wasn't really considering that. I did a quick check of wslview's code once more and found that it can also run arbitrary .exes when given a Windows-style path (starts with LETTER:). Confirmed this by passing the absolute Windows path to powershell.exe to wslview. So this concern may also exist in another form by attempting to use wslview.

    Thinking in a different direction, how do we feel about this:

    1. Detect if Linux is running in WSL
    2. If so, query the registry for the host Windows instance's default browser
    3. Translate the result, which will be a Windows path to the browser and some CLI options. For example, for chrome, this is C:\Program Files\Google\Chrome\Application\chrome.exe" --single-argument %1, and will need to be translated to /mnt/c/Program Files/.../chrome.exe.

    I feel like this would be a security win, as it will pass the (maybe) URL directly to the browser's executable instead of having Windows try to start it with either cmd.exe or powershell.exe, as wslview currently does.

    The downside is that it adds a lot of complexity for this one case. This would be much easier if browser installations just added the browser to PATH by default 😩

  14. andyfeller commented on Feb 20, 2025

    @andyfeller
    Contributor

    is there a reasonable way that we can detect and handle WSL Linux situations more specifically?

    Is there some advantage you can see by doing that? I can't think of a way in which the behaviour would change given this knowledge.

    @williammartin : Let me talk through the concern citing this section draft PR:

    // providersForUrl returns a slice of possible providers to open a browser on linux. The
    // order is from most to least preferred.
    func providersForUrl(url string) []string {
    	base := []string{"xdg-open", "x-www-browser", "www-browser"}
    	// explorer.exe is available in WSL. It can open http:// and https://, but not
    	// file://. explorer.exe is preferred over wslview for performance and to avoid
    	// redundant requests.
    	if strings.HasPrefix(url, "http://") || strings.HasPrefix(url, "https://") {
    		base = append(base, "explorer.exe", "wslview")
    	} else {
    		base = append(base, "wslview")
    	}
    	return base
    }
    • cli/browser has separate build constraints for darwin, freebsd, linux, netbsd, openbsd, windows
    • go1.22 has various other options I'm unsure if they fall under linux if not specified including aix, android, dragonfly, illumos, ios, js, plan9, solaris, wasip1
    • if we proceed with checking for explorer.exe or some other Windows-only command available in WSL without better targeting, we're introducing logic that will run and maybe affect other distributions

    I know openBrowser will check to see if they're on the path before trying to use them. I suppose I'm trying to limit the scope of changes to only the affected OS even though it shouldn't hopefully be an issue for the others.

  15. spenserblack commented on Feb 20, 2025

    @spenserblack
    Contributor

    Wow, I just realized that recent versions of xdg-open seem to already support WSL:

    https://gitlab.freedesktop.org/xdg/xdg-utils/-/blob/503356e79c7357344fe58fe5183dafef93e646dc/scripts/xdg-utils-common.in#L398
    https://gitlab.freedesktop.org/xdg/xdg-utils/-/blob/503356e79c7357344fe58fe5183dafef93e646dc/scripts/xdg-open.in#L458

    They use rundll32.exe url.dll,FileProtocolHJandler URL. It does seem pretty fast. I'm not knowledgeable enough with rundll32.exe, though, so I can't say for certain if it should be included in cli/browser. But, as support for WSL expands, perhaps WSL support should not be a concern of cli/browser (except for keeping the wslview option), and we should trust xdg-open and the other providers to have support in the future?

    Personally, I think I'll be configuring my environment to point towards Windows executables where it makes sense, rather than relying on interop tools to do that for me.

  16. williammartin commented on Feb 20, 2025

    @williammartin
    MemberAuthor

    if we proceed with checking for explorer.exe or some other Windows-only command available in WSL without better targeting, we're introducing logic that will run and maybe affect other distributions

    Right, but the current approach (which it seems like we're moving away from anyway) only puts precedence for explorer.exe over any distribution that has wslview, which we can probably assume is only WSL? Maybe that's naive?

    It does seem like we could take some steps though to more tightly scope this for future maintainers.

    Wow, I just realized that recent versions of xdg-open seem to already support WSL:

    Sorry, are you saying that at some point you would expect xdg-open to be on your WSL distribution?

  17. spenserblack commented on Feb 20, 2025

    @spenserblack
    Contributor

    Sorry, are you saying that at some point you would expect xdg-open to be on your WSL distribution?

    I actually already do have xdg-open on my current WSL instance (running Ubuntu). This whole time I've assumed that gh was executing wslview for me -- and while that's true, it wasn't the whole story. I've recently realized it was executing xdg-open, since that has higher priority. But I had BROWSER=wslview set, so xdg-open would then execute wslview 😅

  18. williammartin commented on Feb 20, 2025

    @williammartin
    MemberAuthor

    Haha oh dear. Is there anything more you think we should do here then?

  19. spenserblack commented on Feb 20, 2025

    @spenserblack
    Contributor

    I can't speak for other users, but I've personally become happy with the workaround I've just implemented, which is to set BROWSER to the path of a symbolic link that points to my browser.exe. It's a lot faster and I feel a bit safer now that I know that the URL will 100% be passed to the browser.

    Is there anything more you think we should do here then?

    One, perhaps naive, idea I had was to detect if the linux system is running in WSL as @andyfeller recommended. If so, add the standard Windows install paths to chrome.exe, firefox.exe, iexplorer.exe, etc. to the list of providers. Again, this goes back to the idea that it's safer to not throw cmd.exe (or powershell.exe) into the mix if it can be avoided (it's also faster). It might be dangerous with certain URL values. Since some domains are also common extensions (like .sh), I feel like there's a slight chance someone forgets the protocol and then gets a nasty surprise when wslview opens ./some-dangerous-script.sh instead of https://some-dangerous-script.sh.

  20. williammartin commented on Feb 21, 2025

    @williammartin
    MemberAuthor

    Thanks for your thoughts. For the moment I think I'm content to leave things as-is, though I appreciate your concern about wslview opening .sh files. If we hear more about slowness on WSL I'm happy to revisit, but given this is an area that it's hard to have a lot of confidence in changing, I'd like to hear more evidence this is a real pain that needs to be addressed.

    If you have strong feelings, please reopen!

  21. hansfn commented on Aug 17, 2025

    @hansfn

    Maybe we should consider reopening this as the wslu project is being discontinued / repo archived. For me a tiny wrapper script around explorer.exe to avoid the non-zero exit status feels very robust and fast.

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

Metadata

Metadata

Assignees

Labels

enhancementa request to improve CLI

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions