fix: navigate 后立即推一帧,静态页开流不再等重绘
screencast 帧只在重绘时来,页面可能在 startScreencast 之前 就已画好——静态页于是开流后一直无画面,直到下一次重绘。 navigate 后用 CDP captureScreenshot 抓一张当前视口塞进帧 队列,泵在下一轮就把桥帧文件写出来;泵跳过空 session 的 ack(eager 帧没有可确认的 screencast 帧)。失败静默, screencast 本身仍照常流动。
This commit is contained in:
1 parent
b844624c96
commit
2d840a6c37
2 files changed
+62
-2
No files matched your search
@@ -120,6 +120,7 @@ class Browser:
|
|||||||
wait_until="domcontentloaded")
|
wait_until="domcontentloaded")
|
||||||
self._current_url = self._page.url
|
self._current_url = self._page.url
|
||||||
self._start_screencast()
|
self._start_screencast()
|
||||||
|
self._eager_frame()
|
||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
raise BrowserError("navigate failed: %s" % exc)
|
raise BrowserError("navigate failed: %s" % exc)
|
||||||
result = self._info()
|
result = self._info()
|
||||||
@@ -321,9 +322,35 @@ class Browser:
|
|||||||
self._cdp = None
|
self._cdp = None
|
||||||
self._cdp_page = None
|
self._cdp_page = None
|
||||||
|
|
||||||
|
def _eager_frame(self):
|
||||||
|
"""Push one current-viewport frame into the sink, best-effort.
|
||||||
|
|
||||||
|
Screencast frames only arrive on repaint, and the page may
|
||||||
|
already have painted before startScreencast ran -- so a
|
||||||
|
static page would otherwise show nothing until its next
|
||||||
|
repaint. Capturing the viewport once right after navigate
|
||||||
|
guarantees the bridge file (and any open stream) has a
|
||||||
|
picture within milliseconds of the page loading.
|
||||||
|
"""
|
||||||
|
if self._cdp is None or self._frame_sink is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
result = self._cdp.send(
|
||||||
|
"Page.captureScreenshot",
|
||||||
|
{"format": "jpeg", "quality": 60})
|
||||||
|
data = result.get("data") if isinstance(result, dict) \
|
||||||
|
else None
|
||||||
|
if data:
|
||||||
|
# Empty session id: pump stores it but must not ack
|
||||||
|
# it (there is no screencast frame to acknowledge).
|
||||||
|
self._frame_sink.put(("", data))
|
||||||
|
except Exception: # noqa: BLE001 - screencast still flows
|
||||||
|
pass
|
||||||
|
|
||||||
def pump_screencast(self):
|
def pump_screencast(self):
|
||||||
"""Main-thread only: store the latest frame and ack every
|
"""Main-thread only: store the latest frame and ack every
|
||||||
queued frame (no ack = CDP stops sending after the first).
|
real screencast frame (no ack = CDP stops sending after the
|
||||||
|
first). Eager frames carry no session id and skip ack.
|
||||||
Returns the latest frame data (base64 jpeg) or ''."""
|
Returns the latest frame data (base64 jpeg) or ''."""
|
||||||
if self._frame_sink is None:
|
if self._frame_sink is None:
|
||||||
return ""
|
return ""
|
||||||
@@ -335,7 +362,7 @@ class Browser:
|
|||||||
break
|
break
|
||||||
if data:
|
if data:
|
||||||
latest = data
|
latest = data
|
||||||
if self._cdp is not None:
|
if self._cdp is not None and sid:
|
||||||
try:
|
try:
|
||||||
self._cdp.send("Page.screencastFrameAck",
|
self._cdp.send("Page.screencastFrameAck",
|
||||||
{"sessionId": sid})
|
{"sessionId": sid})
|
||||||
|
|||||||
@@ -59,12 +59,18 @@ class _FakeCDP:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.sent = []
|
self.sent = []
|
||||||
self.handlers = {}
|
self.handlers = {}
|
||||||
|
self.fail_capture = False
|
||||||
|
|
||||||
def on(self, event, handler):
|
def on(self, event, handler):
|
||||||
self.handlers[event] = handler
|
self.handlers[event] = handler
|
||||||
|
|
||||||
def send(self, method, params=None):
|
def send(self, method, params=None):
|
||||||
self.sent.append((method, params or {}))
|
self.sent.append((method, params or {}))
|
||||||
|
if method == "Page.captureScreenshot":
|
||||||
|
if self.fail_capture:
|
||||||
|
raise RuntimeError("capture boom")
|
||||||
|
return {"data": "EAGERDATA"}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class _FakeContext:
|
class _FakeContext:
|
||||||
@@ -259,6 +265,33 @@ class BrowserTest(unittest.TestCase):
|
|||||||
self.assertIn(("Page.screencastFrameAck", {"sessionId": "s1"}),
|
self.assertIn(("Page.screencastFrameAck", {"sessionId": "s1"}),
|
||||||
[(m, p) for m, p in self.page.cdp.sent])
|
[(m, p) for m, p in self.page.cdp.sent])
|
||||||
|
|
||||||
|
def test_navigate_pushes_eager_frame_without_ack(self):
|
||||||
|
from playwright_remote import SimpleQueue
|
||||||
|
sink = SimpleQueue()
|
||||||
|
self.browser.set_frame_sink(sink)
|
||||||
|
result = self.browser.navigate("https://example.com")
|
||||||
|
self.assertTrue(result["ok"])
|
||||||
|
self.assertIn(("Page.captureScreenshot",
|
||||||
|
{"format": "jpeg", "quality": 60}),
|
||||||
|
[(m, p) for m, p in self.page.cdp.sent])
|
||||||
|
frame = self.browser.pump_screencast()
|
||||||
|
self.assertEqual(frame, "EAGERDATA")
|
||||||
|
acks = [(m, p) for m, p in self.page.cdp.sent
|
||||||
|
if m == "Page.screencastFrameAck"]
|
||||||
|
self.assertEqual(acks, [])
|
||||||
|
|
||||||
|
def test_navigate_survives_failed_eager_capture(self):
|
||||||
|
from playwright_remote import SimpleQueue
|
||||||
|
sink = SimpleQueue()
|
||||||
|
self.browser.set_frame_sink(sink)
|
||||||
|
self.page.cdp.fail_capture = True
|
||||||
|
try:
|
||||||
|
result = self.browser.navigate("https://example.com")
|
||||||
|
finally:
|
||||||
|
self.page.cdp.fail_capture = False
|
||||||
|
self.assertTrue(result["ok"])
|
||||||
|
self.assertEqual(self.browser.pump_screencast(), "")
|
||||||
|
|
||||||
def test_live_shot_writes_fixed_file_and_status(self):
|
def test_live_shot_writes_fixed_file_and_status(self):
|
||||||
self.browser.navigate("https://example.com")
|
self.browser.navigate("https://example.com")
|
||||||
out = self.browser.live_shot()
|
out = self.browser.live_shot()
|
||||||
|
|||||||
Reference in new issue
Block a user