Skip to content

Commit d19bfb3

Browse files
authored
Update emrun to latest version (emscripten-core#10046)
* Update emrun to latest version, with better browser process detection for multiprocess browsers. * flake * Fix emrun stdout posting on localhost * Fix test_zzz_emrun to work on Firefox, and clean up after the browser process when done * xxx test * Account also for --profile in EMTEST_BROWSER * Update comments, also fix failure behavior if no psutil is available * Improve readability
1 parent 26feb01 commit d19bfb3

2 files changed

Lines changed: 91 additions & 29 deletions

File tree

emrun.py

Lines changed: 86 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@
4949
# Represents the process object handle to the browser we opened to run the html page.
5050
browser_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.
5462
browser_stdout_handle = sys.stdout
@@ -72,8 +80,9 @@
7280
# So killing browser_process would just kill launcher.exe and not the opera browser itself.
7381
processname_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.
7988
default_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():
253262
user_pref("services.sync.prefs.sync.browser.sessionstore.restore_on_demand", false);
254263
user_pref("browser.sessionstore.restore_on_demand", false);
255264
user_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
258267
user_pref("dom.max_script_run_time", 0);
259268
user_pref("dom.max_chrome_script_run_time", 0);
@@ -303,8 +312,6 @@ def create_emrun_safe_firefox_profile():
303312
user_pref("media.gmp-eme-adobe.lastUpdate", 2147483647);
304313
user_pref("media.gmp-gmpopenh264.lastUpdate", 2147483647);
305314
user_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.
309316
user_pref("datareporting.healthreport.uploadEnabled", false);
310317
user_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)
324331
user_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)
329336
user_pref("browser.privatebrowsing.autostart", true);
@@ -335,8 +342,28 @@ def create_emrun_safe_firefox_profile():
335342
def 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

342369
def 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+
13351389
def 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)

tests/test_browser.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,7 +2477,7 @@ def test_zzz_emrun(self):
24772477
# and the browser will not close as part of the test, pinning down the cwd on Windows and it wouldn't be possible to delete it. Therefore switch away from that directory
24782478
# before launching.
24792479
os.chdir(path_from_root())
2480-
args_base = [PYTHON, path_from_root('emrun'), '--timeout', '30', '--safe_firefox_profile', '--port', '6939', '--verbose', '--log_stdout', os.path.join(outdir, 'stdout.txt'), '--log_stderr', os.path.join(outdir, 'stderr.txt')]
2480+
args_base = [PYTHON, path_from_root('emrun'), '--timeout', '30', '--safe_firefox_profile', '--kill_exit', '--port', '6939', '--verbose', '--log_stdout', os.path.join(outdir, 'stdout.txt'), '--log_stderr', os.path.join(outdir, 'stderr.txt')]
24812481
if EMTEST_BROWSER is not None:
24822482
# If EMTEST_BROWSER carried command line arguments to pass to the browser,
24832483
# (e.g. "firefox -profile /path/to/foo") those can't be passed via emrun,
@@ -2487,18 +2487,20 @@ def test_zzz_emrun(self):
24872487
args_base += ['--browser', browser_path]
24882488
if len(browser_cmd) > 1:
24892489
browser_args = browser_cmd[1:]
2490-
if 'firefox' in browser_path and '-profile' in browser_args:
2490+
if 'firefox' in browser_path and ('-profile' in browser_args or '--profile' in browser_args):
24912491
# emrun uses its own -profile, strip it out
24922492
parser = argparse.ArgumentParser(add_help=False) # otherwise it throws with -headless
24932493
parser.add_argument('-profile')
2494+
parser.add_argument('--profile')
24942495
browser_args = parser.parse_known_args(browser_args)[1]
24952496
if browser_args:
24962497
args_base += ['--browser_args', ' ' + ' '.join(browser_args)]
24972498
for args in [
24982499
args_base,
2499-
args_base + ['--no_private_browsing', '--port', '6941']
2500+
args_base + ['--private_browsing', '--port', '6941']
25002501
]:
25012502
args += [os.path.join(outdir, 'hello_world.html'), '1', '2', '--3']
2503+
print(' '.join(args))
25022504
proc = run_process(args, check=False)
25032505
stdout = open(os.path.join(outdir, 'stdout.txt'), 'r').read()
25042506
stderr = open(os.path.join(outdir, 'stderr.txt'), 'r').read()

0 commit comments

Comments
 (0)