4949# Represents the process object handle to the browser we opened to run the html page.
5050browser_process = None
5151
52+ previous_browser_process_pids = None
53+ current_browser_process_pids = None
54+
55+ navigation_has_occurred = False
56+
57+ # Stores the browser executable that was run with --browser= parameter.
58+ browser_exe = None
59+
5260# If we have routed browser output to file with --log_stdout and/or --log_stderr,
5361# these track the handles.
5462browser_stdout_handle = sys .stdout
7280# So killing browser_process would just kill launcher.exe and not the opera browser itself.
7381processname_killed_atexit = ""
7482
75- # If user does not specify a --hostname parameter, this hostname is used to launch the server.
76- default_webserver_hostname = "localhost"
83+ # Using "0.0.0.0" means "all interfaces", which should allow connecting to this server via LAN
84+ # addresses. Using "localhost" should allow only connecting from local computer.
85+ default_webserver_hostname = '0.0.0.0'
7786
7887# If user does not specify a --port parameter, this port is used to launch the server.
7988default_webserver_port = 6931
@@ -115,7 +124,7 @@ def import_win32api_modules():
115124 global import_win32api_modules_warned_once
116125 if not import_win32api_modules_warned_once :
117126 print (str (e ), file = sys .stderr )
118- print ("Importing Python win32 modules failed! This most likely occurs if you do not have PyWin32 installed! Get it from http ://sourceforge.net/projects /pywin32/" , file = sys .stderr )
127+ print ("Importing Python win32 modules failed! This most likely occurs if you do not have PyWin32 installed! Get it from https ://github.com/mhammond /pywin32/releases " , file = sys .stderr )
119128 import_win32api_modules_warned_once = True
120129 raise
121130
@@ -253,7 +262,7 @@ def create_emrun_safe_firefox_profile():
253262user_pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", false);
254263user_pref("browser.sessionstore.restore_on_demand", false);
255264user_pref("browser.sessionstore.max_resumed_crashes", -1);
256- user_pref("toolkip .startup.max_resumed_crashes", -1);
265+ user_pref("toolkit .startup.max_resumed_crashes", -1);
257266// Don't show the slow script dialog popup
258267user_pref("dom.max_script_run_time", 0);
259268user_pref("dom.max_chrome_script_run_time", 0);
@@ -303,8 +312,6 @@ def create_emrun_safe_firefox_profile():
303312user_pref("media.gmp-eme-adobe.lastUpdate", 2147483647);
304313user_pref("media.gmp-gmpopenh264.lastUpdate", 2147483647);
305314user_pref("datareporting.healthreport.nextDataSubmissionTime", "2147483647000");
306- // Detect directly when executing if asm.js does not validate by throwing an error.
307- user_pref("javascript.options.throw_on_asmjs_validation_failure", true);
308315// Sending Firefox Health Report Telemetry data is not desirable, since these are automated runs.
309316user_pref("datareporting.healthreport.uploadEnabled", false);
310317user_pref("datareporting.healthreport.service.enabled", false);
@@ -323,7 +330,7 @@ def create_emrun_safe_firefox_profile():
323330// Enable SharedArrayBuffer (this profile is for a testing environment, so Spectre/Meltdown don't apply)
324331user_pref("javascript.options.shared_memory", true);
325332''' )
326- if not emrun_options .no_private_browsing :
333+ if emrun_options .private_browsing :
327334 f .write ('''
328335// Start in private browsing mode to not cache anything to disk (everything will be wiped anyway after this run)
329336user_pref("browser.privatebrowsing.autostart", true);
@@ -335,8 +342,28 @@ def create_emrun_safe_firefox_profile():
335342def is_browser_process_alive ():
336343 """Returns whether the browser page we spawned is still running. (note, not
337344 perfect atm, in case we are running in detached mode)"""
338- global browser_process
339- return browser_process and browser_process .poll () is None
345+ global browser_process , current_browser_process_pids , navigation_has_occurred
346+
347+ # If navigation to the web page has not yet occurred, we behave as if the
348+ # browser has not yet even loaded the page, and treat it as if the browser
349+ # is running (as it is just starting up)
350+ if not navigation_has_occurred :
351+ return True
352+
353+ if browser_process and browser_process .poll () is None :
354+ return True
355+
356+ if current_browser_process_pids is not None :
357+ try :
358+ import psutil
359+ for p in current_browser_process_pids :
360+ if psutil .pid_exists (p ['pid' ]):
361+ return True
362+ except Exception :
363+ # Fail gracefully if psutil not available
364+ return True
365+
366+ return False
340367
341368
342369def kill_browser_process ():
@@ -451,15 +478,10 @@ def serve_forever(self, timeout=0.5):
451478 while self .is_running :
452479 now = tick ()
453480 # Did user close browser?
454- if browser_process :
455- browser_quit_code = browser_process .poll ()
456- if browser_quit_code is not None :
457- delete_emrun_safe_firefox_profile ()
458- if not emrun_options .serve_after_close :
459- emrun_options .serve_after_close = True
460- logv ('Warning: emrun got detached from the target browser process (the process quit with code ' + str (browser_quit_code ) + '). Cannot detect when user closes the browser. Behaving as if --serve_after_close was passed in.' )
461- if not emrun_options .browser :
462- logv ('Try passing the --browser=/path/to/browser option to avoid this from occurring. See https://github.com/emscripten-core/emscripten/issues/3234 for more discussion.' )
481+ if not emrun_options .no_browser and not is_browser_process_alive ():
482+ delete_emrun_safe_firefox_profile ()
483+ if not emrun_options .serve_after_close :
484+ self .is_running = False
463485
464486 # Serve HTTP
465487 self .handle_request ()
@@ -486,7 +508,7 @@ def serve_forever(self, timeout=0.5):
486508 if not emrun_not_enabled_nag_printed and page_last_served_time is not None :
487509 time_since_page_serve = now - page_last_served_time
488510 if not have_received_messages and time_since_page_serve > 10 :
489- logi ('The html page you are running is not emrun-capable. Stdout, stderr and exit(returncode) capture will not work. Recompile the application with the --emrun linker flag to enable this, or pass --no_emrun_detect to emrun to hide this check.' )
511+ logv ('The html page you are running is not emrun-capable. Stdout, stderr and exit(returncode) capture will not work. Recompile the application with the --emrun linker flag to enable this, or pass --no_emrun_detect to emrun to hide this check.' )
490512 emrun_not_enabled_nag_printed = True
491513
492514 # Clean up at quit, print any leftover messages in queue.
@@ -513,6 +535,19 @@ def send_head(self):
513535 path = self .translate_path (self .path )
514536 f = None
515537
538+ # A browser has navigated to this page - check which PID got spawned for the browser
539+ global previous_browser_process_pids , current_browser_process_pids , navigation_has_occurred
540+ if not navigation_has_occurred and current_browser_process_pids is None :
541+ running_browser_process_pids = list_processes_by_name (browser_exe )
542+ for p in running_browser_process_pids :
543+ def pid_existed (pid ):
544+ for proc in previous_browser_process_pids :
545+ if proc ['pid' ] == pid :
546+ return True
547+ return False
548+ current_browser_process_pids = list (filter (lambda x : not pid_existed (x ['pid' ]), running_browser_process_pids ))
549+ navigation_has_occurred = True
550+
516551 if os .path .isdir (path ):
517552 if not self .path .endswith ('/' ):
518553 self .send_response (301 )
@@ -578,7 +613,7 @@ def log_message(self, format, *args):
578613
579614 def do_POST (self ):
580615 self .protocol_version = 'HTTP/1.1'
581- global page_exit_code , emrun_options , have_received_messages
616+ global page_exit_code , emrun_options , have_received_messages , browser_exe
582617
583618 (_ , _ , path , query , _ ) = urlsplit (self .path )
584619 logv ('POST: "' + self .path + '" (path: "' + path + '", query: "' + query + '")' )
@@ -1332,6 +1367,25 @@ def unwrap(s):
13321367 return s
13331368
13341369
1370+ def list_processes_by_name (exe_full_path ):
1371+ pids = []
1372+ try :
1373+ import psutil
1374+ for proc in psutil .process_iter ():
1375+ try :
1376+ pinfo = proc .as_dict (attrs = ['pid' , 'name' , 'exe' ])
1377+ if pinfo ['exe' ].lower ().replace ('\\ ' , '/' ) == exe_full_path .lower ().replace ('\\ ' , '/' ):
1378+ pids .append (pinfo )
1379+ except Exception :
1380+ # Fail gracefully if unable to iterate over a specific process
1381+ pass
1382+ except Exception :
1383+ # Fail gracefully if psutil not available
1384+ pass
1385+
1386+ return pids
1387+
1388+
13351389def run ():
13361390 global browser_process , browser_exe , processname_killed_atexit , emrun_options , emrun_not_enabled_nag_printed , ADB
13371391 usage_str = "emrun [emrun_options] filename.html [html_cmdline_options]\n \n where emrun_options specifies command line options for emrun itself, whereas\n html_cmdline_options specifies startup arguments to the program."
@@ -1419,8 +1473,8 @@ def run():
14191473 parser .add_argument ('--log_html' , dest = 'log_html' , action = 'store_true' ,
14201474 help = 'If set, information lines are printed out an HTML-friendly format.' )
14211475
1422- parser .add_argument ('--no_private_browsing ' , dest = 'no_private_browsing ' , action = 'store_true' , default = False ,
1423- help = 'If specified, do not open browser in private/incognito mode.' )
1476+ parser .add_argument ('--private_browsing ' , dest = 'private_browsing ' , action = 'store_true' , default = False ,
1477+ help = 'If specified, opens browser in private/incognito mode.' )
14241478
14251479 parser .add_argument ('serve' , nargs = '*' )
14261480
@@ -1486,7 +1540,10 @@ def run():
14861540 if file_to_serve == '.' or file_to_serve_is_url :
14871541 serve_dir = os .path .abspath ('.' )
14881542 else :
1489- serve_dir = os .path .dirname (os .path .abspath (file_to_serve ))
1543+ if file_to_serve .endswith ('/' ) or file_to_serve .endswith ('\\ ' ) or os .path .isdir (file_to_serve ):
1544+ serve_dir = file_to_serve
1545+ else :
1546+ serve_dir = os .path .dirname (os .path .abspath (file_to_serve ))
14901547 if file_to_serve_is_url :
14911548 url = file_to_serve
14921549 else :
@@ -1569,22 +1626,23 @@ def run():
15691626 elif 'chrome' in browser_exe .lower ():
15701627 processname_killed_atexit = 'chrome'
15711628 browser_args += ['--enable-nacl' , '--enable-pnacl' , '--disable-restore-session-state' , '--enable-webgl' , '--no-default-browser-check' , '--no-first-run' , '--allow-file-access-from-files' ]
1572- if not options .no_private_browsing :
1629+ if options .private_browsing :
15731630 browser_args += ['--incognito' ]
15741631 # if options.no_server:
15751632 # browser_args += ['--disable-web-security']
15761633 elif 'firefox' in browser_exe .lower ():
15771634 processname_killed_atexit = 'firefox'
15781635 elif 'iexplore' in browser_exe .lower ():
15791636 processname_killed_atexit = 'iexplore'
1580- if not options .no_private_browsing :
1637+ if options .private_browsing :
15811638 browser_args += ['-private' ]
15821639 elif 'opera' in browser_exe .lower ():
15831640 processname_killed_atexit = 'opera'
15841641
15851642 # In Windows cmdline, & character delimits multiple commmands, so must use ^ to escape them.
15861643 if browser_exe == 'cmd' :
15871644 url = url .replace ('&' , '^&' )
1645+ url = url .replace ('0.0.0.0' , 'localhost' )
15881646 browser += browser_args + [url ]
15891647
15901648 if options .kill_on_start :
@@ -1610,7 +1668,7 @@ def run(cmd):
16101668 if processname_killed_atexit == 'firefox' and options .safe_firefox_profile and not options .no_browser and not options .android :
16111669 profile_dir = create_emrun_safe_firefox_profile ()
16121670
1613- browser += ['-no-remote' , '-profile' , profile_dir .replace ('\\ ' , '/' )]
1671+ browser += ['-no-remote' , '-- profile' , profile_dir .replace ('\\ ' , '/' )]
16141672
16151673 if options .system_info :
16161674 logi ('Time of run: ' + time .strftime ("%x %X" ))
@@ -1647,6 +1705,8 @@ def run(cmd):
16471705 # if browser[0] == 'cmd':
16481706 # Workaround an issue where passing 'cmd /C start' is not able to detect when the user closes the page.
16491707 # serve_forever = True
1708+ global previous_browser_process_pids
1709+ previous_browser_process_pids = list_processes_by_name (browser [0 ])
16501710 browser_process = subprocess .Popen (browser , env = subprocess_env ())
16511711 if options .kill_on_exit :
16521712 atexit .register (kill_browser_process )
0 commit comments