44using System . Collections . Immutable ;
55using System . Diagnostics ;
66using System . Diagnostics . CodeAnalysis ;
7+ using System . Runtime . CompilerServices ;
78using Microsoft . Build . Graph ;
89using Microsoft . DotNet . HotReload ;
910using Microsoft . Extensions . Logging ;
1011
1112namespace Microsoft . DotNet . Watch ;
1213
13- internal sealed class BrowserLauncher ( ILogger logger , EnvironmentOptions environmentOptions )
14+ internal sealed class BrowserLauncher ( ILogger logger , IProcessOutputReporter processOutputReporter , EnvironmentOptions environmentOptions )
1415{
1516 // interlocked
1617 private ImmutableHashSet < ProjectInstanceId > _browserLaunchAttempted = [ ] ;
@@ -61,18 +62,13 @@ public static string GetLaunchUrl(string? profileLaunchUrl, string outputLaunchU
6162
6263 private void LaunchBrowser ( string launchUrl , BrowserRefreshServer ? server )
6364 {
64- var fileName = launchUrl ;
65+ var ( fileName , arg , useShellExecute ) = environmentOptions . BrowserPath is { } browserPath
66+ ? ( browserPath , launchUrl , false )
67+ : ( launchUrl , null , true ) ;
6568
66- var args = string . Empty ;
67- if ( environmentOptions . BrowserPath is { } browserPath )
68- {
69- args = fileName ;
70- fileName = browserPath ;
71- }
69+ logger . Log ( MessageDescriptor . LaunchingBrowser , fileName , arg ) ;
7270
73- logger . Log ( MessageDescriptor . LaunchingBrowser , fileName , args ) ;
74-
75- if ( environmentOptions . TestFlags != TestFlags . None )
71+ if ( environmentOptions . TestFlags != TestFlags . None && environmentOptions . BrowserPath == null )
7672 {
7773 if ( environmentOptions . TestFlags . HasFlag ( TestFlags . MockBrowser ) )
7874 {
@@ -83,29 +79,23 @@ private void LaunchBrowser(string launchUrl, BrowserRefreshServer? server)
8379 return ;
8480 }
8581
86- var info = new ProcessStartInfo
82+ // dotnet-watch, by default, relies on URL file association to launch browsers. On Windows and MacOS, this works fairly well
83+ // where URLs are associated with the default browser. On Linux, this is a bit murky.
84+ // From emperical observation, it's noted that failing to launch a browser results in either Process.Start returning a null-value
85+ // or for the process to have immediately exited.
86+ // We can use this to provide a helpful message.
87+ var processSpec = new ProcessSpec ( )
8788 {
88- FileName = fileName ,
89- Arguments = args ,
90- UseShellExecute = true ,
89+ Executable = fileName ,
90+ Arguments = arg != null ? [ arg ] : [ ] ,
91+ UseShellExecute = useShellExecute ,
92+ OnOutput = environmentOptions . TestFlags . HasFlag ( TestFlags . RedirectBrowserOutput ) ? processOutputReporter . ReportOutput : null ,
9193 } ;
9294
93- try
94- {
95- using var browserProcess = Process . Start ( info ) ;
96- if ( browserProcess is null or { HasExited : true } )
97- {
98- // dotnet-watch, by default, relies on URL file association to launch browsers. On Windows and MacOS, this works fairly well
99- // where URLs are associated with the default browser. On Linux, this is a bit murky.
100- // From emperical observation, it's noted that failing to launch a browser results in either Process.Start returning a null-value
101- // or for the process to have immediately exited.
102- // We can use this to provide a helpful message.
103- logger . LogInformation ( "Unable to launch the browser. Url '{Url}'." , launchUrl ) ;
104- }
105- }
106- catch ( Exception e )
95+ using var browserProcess = ProcessRunner . TryStartProcess ( processSpec , logger ) ;
96+ if ( browserProcess is null or { HasExited : true } )
10797 {
108- logger . LogDebug ( "Failed to launch a browser: {Message} ", e . Message ) ;
98+ logger . LogWarning ( "Unable to launch the browser. Url '{Url}'. ", launchUrl ) ;
10999 }
110100 }
111101
0 commit comments