vicentemovil commited on
Commit
e6f69ae
·
1 Parent(s): df51c50

Fix page load: launch Chromium with the URL and poll readyState instead of relying on Page.navigate+loadEventFired

Browse files
Files changed (1) hide show
  1. app.py +92 -48
app.py CHANGED
@@ -344,47 +344,66 @@ def _cp(path: str | None) -> str | None:
344
  return dst
345
 
346
 
347
- def _launch_chromium(w: int, h: int):
348
- """Launch headless Chromium with CDP enabled. Returns (proc, port)."""
 
 
 
 
 
349
  port = get_free_port()
350
  chromium = get_chromium_path()
351
- proc = subprocess.Popen(
352
- [
353
- chromium,
354
- f"--remote-debugging-port={port}",
355
- "--headless",
356
- "--no-sandbox",
357
- "--disable-dev-shm-usage",
358
- "--disable-setuid-sandbox",
359
- "--use-gl=swiftshader",
360
- "--enable-webgl",
361
- "--hide-scrollbars",
362
- "--autoplay-policy=no-user-gesture-required",
363
- f"--window-size={w},{h}",
364
- ],
365
- stdout=subprocess.DEVNULL,
366
- stderr=subprocess.DEVNULL,
367
- )
 
368
  return proc, port
369
 
370
 
371
- async def _connect_cdp(port: int, log):
372
  """Wait for the Chrome DevTools websocket URL and return it.
373
 
374
  /json/list returns several targets (extension background pages, browser_ui,
375
- etc.). We must pick the actual tab (type == "page") — pages[0] may be an
376
- extension background_page, and navigating that would never reach the URL.
377
  """
 
 
 
 
 
378
  async with aiohttp.ClientSession() as session:
379
  for attempt in range(20):
380
  await asyncio.sleep(0.5)
381
  try:
382
  async with session.get(f"http://localhost:{port}/json/list") as resp:
383
  targets = await resp.json()
384
- page = next((t for t in targets if t.get("type") == "page"), None)
385
- if page:
386
- log(f"chrome devtools ready after {attempt+1} attempts")
387
- return page["webSocketDebuggerUrl"]
 
 
 
 
 
 
 
 
388
  except Exception as e:
389
  log(f"devtools attempt {attempt+1} failed: {e}")
390
  continue
@@ -392,29 +411,54 @@ async def _connect_cdp(port: int, log):
392
 
393
 
394
  async def _navigate_and_wait(ws, send, url, log):
395
- """Navigate to url and wait for Page.loadEventFired (max 15s). Returns False on timeout."""
 
 
 
 
 
 
 
 
 
 
396
  log(f"navigating to {url}")
397
- nav_id = await send("Page.navigate", {"url": url})
398
- deadline = time.monotonic() + 10
399
- while time.monotonic() < deadline:
400
- try:
401
- raw = await asyncio.wait_for(ws.recv(), timeout=1.0)
402
- if json.loads(raw).get("id") == nav_id:
403
- log("Page.navigate acknowledged")
404
- break
405
- except asyncio.TimeoutError:
406
- continue
407
-
408
- log("waiting for Page.loadEventFired (max 15s)")
409
  deadline = time.monotonic() + 15
410
  while time.monotonic() < deadline:
411
  try:
412
- raw = await asyncio.wait_for(ws.recv(), timeout=1.0)
413
- if json.loads(raw).get("method") == "Page.loadEventFired":
414
- log("Page.loadEventFired received")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  return True
416
- except asyncio.TimeoutError:
417
- continue
 
 
418
  log("load timeout — proceeding anyway")
419
  return False
420
 
@@ -436,10 +480,10 @@ async def _record_async(
436
  chromium = get_chromium_path()
437
  log(f"launching chromium on port {port}, url={url}, size={w}x{h}, duration={duration}")
438
 
439
- proc, port = _launch_chromium(w, h)
440
 
441
  try:
442
- ws_url = await _connect_cdp(port, log)
443
 
444
  if not ws_url:
445
  raise gr.Error("Could not connect to Chrome DevTools.")
@@ -541,10 +585,10 @@ async def _capture_async(
541
  print(f"[wr {time.monotonic()-t0:.1f}s] {msg}", flush=True)
542
 
543
  log(f"launching chromium, url={url}, size={w}x{h}")
544
- proc, port = _launch_chromium(w, h)
545
 
546
  try:
547
- ws_url = await _connect_cdp(port, log)
548
  if not ws_url:
549
  raise gr.Error("Could not connect to Chrome DevTools.")
550
 
 
344
  return dst
345
 
346
 
347
+ def _launch_chromium(w: int, h: int, url: str | None = None):
348
+ """Launch headless Chromium with CDP enabled. Returns (proc, port).
349
+
350
+ url is passed at launch so the initial tab IS the target page — navigating
351
+ an empty chrome://newtab tab away via Page.navigate is unreliable on some
352
+ chromium builds (it can silently stay on the new-tab page).
353
+ """
354
  port = get_free_port()
355
  chromium = get_chromium_path()
356
+ cmd = [
357
+ chromium,
358
+ f"--remote-debugging-port={port}",
359
+ "--headless",
360
+ "--no-sandbox",
361
+ "--disable-dev-shm-usage",
362
+ "--disable-setuid-sandbox",
363
+ "--use-gl=swiftshader",
364
+ "--enable-webgl",
365
+ "--hide-scrollbars",
366
+ "--autoplay-policy=no-user-gesture-required",
367
+ "--no-first-run",
368
+ "--no-default-browser-check",
369
+ f"--window-size={w},{h}",
370
+ ]
371
+ if url:
372
+ cmd.append(url)
373
+ proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
374
  return proc, port
375
 
376
 
377
+ async def _connect_cdp(port: int, log, target_url: str | None = None):
378
  """Wait for the Chrome DevTools websocket URL and return it.
379
 
380
  /json/list returns several targets (extension background pages, browser_ui,
381
+ etc.). Prefer a type == "page" target; if target_url is given, prefer a page
382
+ whose URL already matches its host (launch-with-url case).
383
  """
384
+ host = None
385
+ if target_url:
386
+ from urllib.parse import urlparse
387
+ host = urlparse(target_url).netloc
388
+
389
  async with aiohttp.ClientSession() as session:
390
  for attempt in range(20):
391
  await asyncio.sleep(0.5)
392
  try:
393
  async with session.get(f"http://localhost:{port}/json/list") as resp:
394
  targets = await resp.json()
395
+ pages = [t for t in targets if t.get("type") == "page"]
396
+ if not pages:
397
+ continue
398
+ chosen = None
399
+ if host:
400
+ chosen = next(
401
+ (t for t in pages if host in (t.get("url") or "")),
402
+ None,
403
+ )
404
+ chosen = chosen or pages[0]
405
+ log(f"chrome devtools ready after {attempt+1} attempts (target={chosen.get('url')})")
406
+ return chosen["webSocketDebuggerUrl"]
407
  except Exception as e:
408
  log(f"devtools attempt {attempt+1} failed: {e}")
409
  continue
 
411
 
412
 
413
  async def _navigate_and_wait(ws, send, url, log):
414
+ """Ensure the tab is on `url` and wait for it to finish loading.
415
+
416
+ Sends Page.navigate (idempotent when the tab already holds the URL from the
417
+ launch argument), then polls document.readyState via Runtime.evaluate so we
418
+ don't miss Page.loadEventFired (which fires before we enable Page in the
419
+ launch-with-url case). Returns True when ready, False on timeout (callers
420
+ proceed anyway).
421
+ """
422
+ from urllib.parse import urlparse
423
+ host = urlparse(url).netloc
424
+
425
  log(f"navigating to {url}")
426
+ await send("Page.navigate", {"url": url})
427
+ await send("Runtime.enable")
428
+
 
 
 
 
 
 
 
 
 
429
  deadline = time.monotonic() + 15
430
  while time.monotonic() < deadline:
431
  try:
432
+ eval_id = await send(
433
+ "Runtime.evaluate",
434
+ {
435
+ "expression": "JSON.stringify({href: location.href, ready: document.readyState})",
436
+ "returnByValue": True,
437
+ },
438
+ )
439
+ state = None
440
+ end = time.monotonic() + 2
441
+ while time.monotonic() < end:
442
+ try:
443
+ raw = await asyncio.wait_for(ws.recv(), timeout=1.0)
444
+ except asyncio.TimeoutError:
445
+ continue
446
+ obj = json.loads(raw)
447
+ if obj.get("id") == eval_id:
448
+ val = (obj.get("result", {}).get("result", {}) or {}).get("value")
449
+ if val:
450
+ try:
451
+ state = json.loads(val)
452
+ except (json.JSONDecodeError, ValueError):
453
+ state = None
454
+ break
455
+ if state and state.get("ready") == "complete" and host in (state.get("href") or ""):
456
+ log(f"page ready: {state.get('href')}")
457
  return True
458
+ except Exception as e:
459
+ log(f"state poll error: {e}")
460
+ await asyncio.sleep(0.5)
461
+
462
  log("load timeout — proceeding anyway")
463
  return False
464
 
 
480
  chromium = get_chromium_path()
481
  log(f"launching chromium on port {port}, url={url}, size={w}x{h}, duration={duration}")
482
 
483
+ proc, port = _launch_chromium(w, h, url)
484
 
485
  try:
486
+ ws_url = await _connect_cdp(port, log, url)
487
 
488
  if not ws_url:
489
  raise gr.Error("Could not connect to Chrome DevTools.")
 
585
  print(f"[wr {time.monotonic()-t0:.1f}s] {msg}", flush=True)
586
 
587
  log(f"launching chromium, url={url}, size={w}x{h}")
588
+ proc, port = _launch_chromium(w, h, url)
589
 
590
  try:
591
+ ws_url = await _connect_cdp(port, log, url)
592
  if not ws_url:
593
  raise gr.Error("Could not connect to Chrome DevTools.")
594