_browser_context.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. # Copyright (c) Microsoft Corporation.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import asyncio
  15. import json
  16. from pathlib import Path
  17. from types import SimpleNamespace
  18. from typing import (
  19. TYPE_CHECKING,
  20. Any,
  21. Callable,
  22. Dict,
  23. List,
  24. Literal,
  25. Optional,
  26. Pattern,
  27. Sequence,
  28. Set,
  29. Union,
  30. cast,
  31. )
  32. from playwright._impl._api_structures import (
  33. Cookie,
  34. Geolocation,
  35. SetCookieParam,
  36. StorageState,
  37. )
  38. from playwright._impl._artifact import Artifact
  39. from playwright._impl._cdp_session import CDPSession
  40. from playwright._impl._clock import Clock
  41. from playwright._impl._connection import (
  42. ChannelOwner,
  43. from_channel,
  44. from_nullable_channel,
  45. )
  46. from playwright._impl._console_message import ConsoleMessage
  47. from playwright._impl._dialog import Dialog
  48. from playwright._impl._errors import Error, TargetClosedError
  49. from playwright._impl._event_context_manager import EventContextManagerImpl
  50. from playwright._impl._fetch import APIRequestContext
  51. from playwright._impl._frame import Frame
  52. from playwright._impl._har_router import HarRouter
  53. from playwright._impl._helper import (
  54. HarContentPolicy,
  55. HarMode,
  56. HarRecordingMetadata,
  57. RouteFromHarNotFoundPolicy,
  58. RouteHandler,
  59. RouteHandlerCallback,
  60. TimeoutSettings,
  61. URLMatch,
  62. WebSocketRouteHandlerCallback,
  63. async_readfile,
  64. async_writefile,
  65. locals_to_params,
  66. parse_error,
  67. to_impl,
  68. )
  69. from playwright._impl._network import (
  70. Request,
  71. Response,
  72. Route,
  73. WebSocketRoute,
  74. WebSocketRouteHandler,
  75. serialize_headers,
  76. )
  77. from playwright._impl._page import BindingCall, Page, Worker
  78. from playwright._impl._str_utils import escape_regex_flags
  79. from playwright._impl._tracing import Tracing
  80. from playwright._impl._waiter import Waiter
  81. from playwright._impl._web_error import WebError
  82. if TYPE_CHECKING: # pragma: no cover
  83. from playwright._impl._browser import Browser
  84. class BrowserContext(ChannelOwner):
  85. Events = SimpleNamespace(
  86. # Deprecated in v1.56, never emitted anymore.
  87. BackgroundPage="backgroundpage",
  88. Close="close",
  89. Console="console",
  90. Dialog="dialog",
  91. Page="page",
  92. WebError="weberror",
  93. ServiceWorker="serviceworker",
  94. Request="request",
  95. Response="response",
  96. RequestFailed="requestfailed",
  97. RequestFinished="requestfinished",
  98. )
  99. def __init__(
  100. self, parent: ChannelOwner, type: str, guid: str, initializer: Dict
  101. ) -> None:
  102. super().__init__(parent, type, guid, initializer)
  103. # Browser is null for browser contexts created outside of normal browser, e.g. android or electron.
  104. # circular import workaround:
  105. self._browser: Optional["Browser"] = None
  106. if parent.__class__.__name__ == "Browser":
  107. self._browser = cast("Browser", parent)
  108. self._pages: List[Page] = []
  109. self._routes: List[RouteHandler] = []
  110. self._web_socket_routes: List[WebSocketRouteHandler] = []
  111. self._bindings: Dict[str, Any] = {}
  112. self._timeout_settings = TimeoutSettings(None)
  113. self._owner_page: Optional[Page] = None
  114. self._options: Dict[str, Any] = initializer["options"]
  115. self._service_workers: Set[Worker] = set()
  116. self._base_url: Optional[str] = self._options.get("baseURL")
  117. self._videos_dir: Optional[str] = self._options.get("recordVideo")
  118. self._tracing = cast(Tracing, from_channel(initializer["tracing"]))
  119. self._har_recorders: Dict[str, HarRecordingMetadata] = {}
  120. self._request: APIRequestContext = from_channel(initializer["requestContext"])
  121. self._clock = Clock(self)
  122. self._channel.on(
  123. "bindingCall",
  124. lambda params: self._on_binding(from_channel(params["binding"])),
  125. )
  126. self._channel.on("close", lambda _: self._on_close())
  127. self._channel.on(
  128. "page", lambda params: self._on_page(from_channel(params["page"]))
  129. )
  130. self._channel.on(
  131. "route",
  132. lambda params: self._loop.create_task(
  133. self._on_route(
  134. from_channel(params.get("route")),
  135. )
  136. ),
  137. )
  138. self._channel.on(
  139. "webSocketRoute",
  140. lambda params: self._loop.create_task(
  141. self._on_web_socket_route(
  142. from_channel(params["webSocketRoute"]),
  143. )
  144. ),
  145. )
  146. self._channel.on(
  147. "serviceWorker",
  148. lambda params: self._on_service_worker(from_channel(params["worker"])),
  149. )
  150. self._channel.on(
  151. "console",
  152. lambda event: self._on_console_message(event),
  153. )
  154. self._channel.on(
  155. "dialog", lambda params: self._on_dialog(from_channel(params["dialog"]))
  156. )
  157. self._channel.on(
  158. "pageError",
  159. lambda params: self._on_page_error(
  160. parse_error(params["error"]["error"]),
  161. from_nullable_channel(params["page"]),
  162. ),
  163. )
  164. self._channel.on(
  165. "request",
  166. lambda params: self._on_request(
  167. from_channel(params["request"]),
  168. from_nullable_channel(params.get("page")),
  169. ),
  170. )
  171. self._channel.on(
  172. "response",
  173. lambda params: self._on_response(
  174. from_channel(params["response"]),
  175. from_nullable_channel(params.get("page")),
  176. ),
  177. )
  178. self._channel.on(
  179. "requestFailed",
  180. lambda params: self._on_request_failed(
  181. from_channel(params["request"]),
  182. params["responseEndTiming"],
  183. params.get("failureText"),
  184. from_nullable_channel(params.get("page")),
  185. ),
  186. )
  187. self._channel.on(
  188. "requestFinished",
  189. lambda params: self._on_request_finished(
  190. from_channel(params["request"]),
  191. from_nullable_channel(params.get("response")),
  192. params["responseEndTiming"],
  193. from_nullable_channel(params.get("page")),
  194. ),
  195. )
  196. self._closed_future: asyncio.Future = asyncio.Future()
  197. self.once(
  198. self.Events.Close, lambda context: self._closed_future.set_result(True)
  199. )
  200. self._close_reason: Optional[str] = None
  201. self._har_routers: List[HarRouter] = []
  202. self._set_event_to_subscription_mapping(
  203. {
  204. BrowserContext.Events.Console: "console",
  205. BrowserContext.Events.Dialog: "dialog",
  206. BrowserContext.Events.Request: "request",
  207. BrowserContext.Events.Response: "response",
  208. BrowserContext.Events.RequestFinished: "requestFinished",
  209. BrowserContext.Events.RequestFailed: "requestFailed",
  210. }
  211. )
  212. self._closing_or_closed = False
  213. def __repr__(self) -> str:
  214. return f"<BrowserContext browser={self.browser}>"
  215. def _on_page(self, page: Page) -> None:
  216. self._pages.append(page)
  217. self.emit(BrowserContext.Events.Page, page)
  218. if page._opener and not page._opener.is_closed():
  219. page._opener.emit(Page.Events.Popup, page)
  220. async def _on_route(self, route: Route) -> None:
  221. route._context = self
  222. page = route.request._safe_page()
  223. route_handlers = self._routes.copy()
  224. for route_handler in route_handlers:
  225. # If the page or the context was closed we stall all requests right away.
  226. if (page and page._close_was_called) or self._closing_or_closed:
  227. return
  228. if not route_handler.matches(route.request.url):
  229. continue
  230. if route_handler not in self._routes:
  231. continue
  232. if route_handler.will_expire:
  233. self._routes.remove(route_handler)
  234. try:
  235. handled = await route_handler.handle(route)
  236. finally:
  237. if len(self._routes) == 0:
  238. asyncio.create_task(
  239. self._connection.wrap_api_call(
  240. lambda: self._update_interception_patterns(), True
  241. )
  242. )
  243. if handled:
  244. return
  245. try:
  246. # If the page is closed or unrouteAll() was called without waiting and interception disabled,
  247. # the method will throw an error - silence it.
  248. await route._inner_continue(True)
  249. except Exception:
  250. pass
  251. async def _on_web_socket_route(self, web_socket_route: WebSocketRoute) -> None:
  252. route_handler = next(
  253. (
  254. route_handler
  255. for route_handler in self._web_socket_routes
  256. if route_handler.matches(web_socket_route.url)
  257. ),
  258. None,
  259. )
  260. if route_handler:
  261. await route_handler.handle(web_socket_route)
  262. else:
  263. web_socket_route.connect_to_server()
  264. def _on_binding(self, binding_call: BindingCall) -> None:
  265. func = self._bindings.get(binding_call._initializer["name"])
  266. if func is None:
  267. return
  268. asyncio.create_task(binding_call.call(func))
  269. def set_default_navigation_timeout(self, timeout: float) -> None:
  270. return self._set_default_navigation_timeout_impl(timeout)
  271. def _set_default_navigation_timeout_impl(self, timeout: Optional[float]) -> None:
  272. self._timeout_settings.set_default_navigation_timeout(timeout)
  273. def set_default_timeout(self, timeout: float) -> None:
  274. return self._set_default_timeout_impl(timeout)
  275. def _set_default_timeout_impl(self, timeout: Optional[float]) -> None:
  276. self._timeout_settings.set_default_timeout(timeout)
  277. @property
  278. def pages(self) -> List[Page]:
  279. return self._pages.copy()
  280. @property
  281. def browser(self) -> Optional["Browser"]:
  282. return self._browser
  283. async def _initialize_har_from_options(
  284. self,
  285. record_har_path: Optional[Union[Path, str]],
  286. record_har_content: Optional[HarContentPolicy],
  287. record_har_omit_content: Optional[bool],
  288. record_har_url_filter: Optional[Union[Pattern[str], str]],
  289. record_har_mode: Optional[HarMode],
  290. ) -> None:
  291. if not record_har_path:
  292. return
  293. record_har_path = str(record_har_path)
  294. default_policy: HarContentPolicy = (
  295. "attach" if record_har_path.endswith(".zip") else "embed"
  296. )
  297. content_policy: HarContentPolicy = record_har_content or (
  298. "omit" if record_har_omit_content is True else default_policy
  299. )
  300. await self._record_into_har(
  301. har=record_har_path,
  302. page=None,
  303. url=record_har_url_filter,
  304. update_content=content_policy,
  305. update_mode=(record_har_mode or "full"),
  306. )
  307. async def new_page(self) -> Page:
  308. if self._owner_page:
  309. raise Error("Please use browser.new_context()")
  310. return from_channel(await self._channel.send("newPage", None))
  311. async def cookies(self, urls: Union[str, Sequence[str]] = None) -> List[Cookie]:
  312. if urls is None:
  313. urls = []
  314. if isinstance(urls, str):
  315. urls = [urls]
  316. return await self._channel.send("cookies", None, dict(urls=urls))
  317. async def add_cookies(self, cookies: Sequence[SetCookieParam]) -> None:
  318. await self._channel.send("addCookies", None, dict(cookies=cookies))
  319. async def clear_cookies(
  320. self,
  321. name: Union[str, Pattern[str]] = None,
  322. domain: Union[str, Pattern[str]] = None,
  323. path: Union[str, Pattern[str]] = None,
  324. ) -> None:
  325. await self._channel.send(
  326. "clearCookies",
  327. None,
  328. {
  329. "name": name if isinstance(name, str) else None,
  330. "nameRegexSource": name.pattern if isinstance(name, Pattern) else None,
  331. "nameRegexFlags": (
  332. escape_regex_flags(name) if isinstance(name, Pattern) else None
  333. ),
  334. "domain": domain if isinstance(domain, str) else None,
  335. "domainRegexSource": (
  336. domain.pattern if isinstance(domain, Pattern) else None
  337. ),
  338. "domainRegexFlags": (
  339. escape_regex_flags(domain) if isinstance(domain, Pattern) else None
  340. ),
  341. "path": path if isinstance(path, str) else None,
  342. "pathRegexSource": path.pattern if isinstance(path, Pattern) else None,
  343. "pathRegexFlags": (
  344. escape_regex_flags(path) if isinstance(path, Pattern) else None
  345. ),
  346. },
  347. )
  348. async def grant_permissions(
  349. self, permissions: Sequence[str], origin: str = None
  350. ) -> None:
  351. await self._channel.send("grantPermissions", None, locals_to_params(locals()))
  352. async def clear_permissions(self) -> None:
  353. await self._channel.send("clearPermissions", None)
  354. async def set_geolocation(self, geolocation: Geolocation = None) -> None:
  355. await self._channel.send("setGeolocation", None, locals_to_params(locals()))
  356. async def set_extra_http_headers(self, headers: Dict[str, str]) -> None:
  357. await self._channel.send(
  358. "setExtraHTTPHeaders", None, dict(headers=serialize_headers(headers))
  359. )
  360. async def set_offline(self, offline: bool) -> None:
  361. await self._channel.send("setOffline", None, dict(offline=offline))
  362. async def add_init_script(
  363. self, script: str = None, path: Union[str, Path] = None
  364. ) -> None:
  365. if path:
  366. script = (await async_readfile(path)).decode()
  367. if not isinstance(script, str):
  368. raise Error("Either path or script parameter must be specified")
  369. await self._channel.send("addInitScript", None, dict(source=script))
  370. async def expose_binding(
  371. self, name: str, callback: Callable, handle: bool = None
  372. ) -> None:
  373. for page in self._pages:
  374. if name in page._bindings:
  375. raise Error(
  376. f'Function "{name}" has been already registered in one of the pages'
  377. )
  378. if name in self._bindings:
  379. raise Error(f'Function "{name}" has been already registered')
  380. self._bindings[name] = callback
  381. await self._channel.send(
  382. "exposeBinding", None, dict(name=name, needsHandle=handle or False)
  383. )
  384. async def expose_function(self, name: str, callback: Callable) -> None:
  385. await self.expose_binding(name, lambda source, *args: callback(*args))
  386. async def route(
  387. self, url: URLMatch, handler: RouteHandlerCallback, times: int = None
  388. ) -> None:
  389. self._routes.insert(
  390. 0,
  391. RouteHandler(
  392. self._base_url,
  393. url,
  394. handler,
  395. True if self._dispatcher_fiber else False,
  396. times,
  397. ),
  398. )
  399. await self._update_interception_patterns()
  400. async def unroute(
  401. self, url: URLMatch, handler: Optional[RouteHandlerCallback] = None
  402. ) -> None:
  403. removed = []
  404. remaining = []
  405. for route in self._routes:
  406. if route.url != url or (handler and route.handler != handler):
  407. remaining.append(route)
  408. else:
  409. removed.append(route)
  410. await self._unroute_internal(removed, remaining, "default")
  411. async def _unroute_internal(
  412. self,
  413. removed: List[RouteHandler],
  414. remaining: List[RouteHandler],
  415. behavior: Literal["default", "ignoreErrors", "wait"] = None,
  416. ) -> None:
  417. self._routes = remaining
  418. if behavior is not None and behavior != "default":
  419. await asyncio.gather(*map(lambda router: router.stop(behavior), removed)) # type: ignore
  420. await self._update_interception_patterns()
  421. async def route_web_socket(
  422. self, url: URLMatch, handler: WebSocketRouteHandlerCallback
  423. ) -> None:
  424. self._web_socket_routes.insert(
  425. 0,
  426. WebSocketRouteHandler(self._base_url, url, handler),
  427. )
  428. await self._update_web_socket_interception_patterns()
  429. def _dispose_har_routers(self) -> None:
  430. for router in self._har_routers:
  431. router.dispose()
  432. self._har_routers = []
  433. async def unroute_all(
  434. self, behavior: Literal["default", "ignoreErrors", "wait"] = None
  435. ) -> None:
  436. await self._unroute_internal(self._routes, [], behavior)
  437. self._dispose_har_routers()
  438. async def _record_into_har(
  439. self,
  440. har: Union[Path, str],
  441. page: Optional[Page] = None,
  442. url: Union[Pattern[str], str] = None,
  443. update_content: HarContentPolicy = None,
  444. update_mode: HarMode = None,
  445. ) -> None:
  446. update_content = update_content or "attach"
  447. params: Dict[str, Any] = {
  448. "options": {
  449. "zip": str(har).endswith(".zip"),
  450. "content": update_content,
  451. "urlGlob": url if isinstance(url, str) else None,
  452. "urlRegexSource": url.pattern if isinstance(url, Pattern) else None,
  453. "urlRegexFlags": (
  454. escape_regex_flags(url) if isinstance(url, Pattern) else None
  455. ),
  456. "mode": update_mode or "minimal",
  457. }
  458. }
  459. if page:
  460. params["page"] = page._channel
  461. har_id = await self._channel.send("harStart", None, params)
  462. self._har_recorders[har_id] = {
  463. "path": str(har),
  464. "content": update_content,
  465. }
  466. async def route_from_har(
  467. self,
  468. har: Union[Path, str],
  469. url: Union[Pattern[str], str] = None,
  470. notFound: RouteFromHarNotFoundPolicy = None,
  471. update: bool = None,
  472. updateContent: Literal["attach", "embed"] = None,
  473. updateMode: HarMode = None,
  474. ) -> None:
  475. if update:
  476. await self._record_into_har(
  477. har=har,
  478. page=None,
  479. url=url,
  480. update_content=updateContent,
  481. update_mode=updateMode,
  482. )
  483. return
  484. router = await HarRouter.create(
  485. local_utils=self._connection.local_utils,
  486. file=str(har),
  487. not_found_action=notFound or "abort",
  488. url_matcher=url,
  489. )
  490. self._har_routers.append(router)
  491. await router.add_context_route(self)
  492. async def _update_interception_patterns(self) -> None:
  493. patterns = RouteHandler.prepare_interception_patterns(self._routes)
  494. await self._channel.send(
  495. "setNetworkInterceptionPatterns", None, {"patterns": patterns}
  496. )
  497. async def _update_web_socket_interception_patterns(self) -> None:
  498. patterns = WebSocketRouteHandler.prepare_interception_patterns(
  499. self._web_socket_routes
  500. )
  501. await self._channel.send(
  502. "setWebSocketInterceptionPatterns", None, {"patterns": patterns}
  503. )
  504. def expect_event(
  505. self,
  506. event: str,
  507. predicate: Callable = None,
  508. timeout: float = None,
  509. ) -> EventContextManagerImpl:
  510. if timeout is None:
  511. timeout = self._timeout_settings.timeout()
  512. waiter = Waiter(self, f"browser_context.expect_event({event})")
  513. waiter.reject_on_timeout(
  514. timeout, f'Timeout {timeout}ms exceeded while waiting for event "{event}"'
  515. )
  516. if event != BrowserContext.Events.Close:
  517. waiter.reject_on_event(
  518. self, BrowserContext.Events.Close, lambda: TargetClosedError()
  519. )
  520. waiter.wait_for_event(self, event, predicate)
  521. return EventContextManagerImpl(waiter.result())
  522. def _on_close(self) -> None:
  523. self._closing_or_closed = True
  524. if self._browser:
  525. if self in self._browser._contexts:
  526. self._browser._contexts.remove(self)
  527. assert self._browser._browser_type is not None
  528. if (
  529. self
  530. in self._browser._browser_type._playwright.selectors._contexts_for_selectors
  531. ):
  532. self._browser._browser_type._playwright.selectors._contexts_for_selectors.remove(
  533. self
  534. )
  535. self._dispose_har_routers()
  536. self._tracing._reset_stack_counter()
  537. self.emit(BrowserContext.Events.Close, self)
  538. async def close(self, reason: str = None) -> None:
  539. if self._closing_or_closed:
  540. return
  541. self._close_reason = reason
  542. self._closing_or_closed = True
  543. await self.request.dispose(reason=reason)
  544. async def _inner_close() -> None:
  545. for har_id, params in self._har_recorders.items():
  546. har = cast(
  547. Artifact,
  548. from_channel(
  549. await self._channel.send("harExport", None, {"harId": har_id})
  550. ),
  551. )
  552. # Server side will compress artifact if content is attach or if file is .zip.
  553. is_compressed = params.get("content") == "attach" or params[
  554. "path"
  555. ].endswith(".zip")
  556. need_compressed = params["path"].endswith(".zip")
  557. if is_compressed and not need_compressed:
  558. tmp_path = params["path"] + ".tmp"
  559. await har.save_as(tmp_path)
  560. await self._connection.local_utils.har_unzip(
  561. zipFile=tmp_path, harFile=params["path"]
  562. )
  563. else:
  564. await har.save_as(params["path"])
  565. await har.delete()
  566. await self._channel._connection.wrap_api_call(_inner_close, True)
  567. await self._channel.send("close", None, {"reason": reason})
  568. await self._closed_future
  569. async def storage_state(
  570. self, path: Union[str, Path] = None, indexedDB: bool = None
  571. ) -> StorageState:
  572. result = await self._channel.send_return_as_dict(
  573. "storageState", None, {"indexedDB": indexedDB}
  574. )
  575. if path:
  576. await async_writefile(path, json.dumps(result))
  577. return result
  578. def _effective_close_reason(self) -> Optional[str]:
  579. if self._close_reason:
  580. return self._close_reason
  581. if self._browser:
  582. return self._browser._close_reason
  583. return None
  584. async def wait_for_event(
  585. self, event: str, predicate: Callable = None, timeout: float = None
  586. ) -> Any:
  587. async with self.expect_event(event, predicate, timeout) as event_info:
  588. pass
  589. return await event_info
  590. def expect_console_message(
  591. self,
  592. predicate: Callable[[ConsoleMessage], bool] = None,
  593. timeout: float = None,
  594. ) -> EventContextManagerImpl[ConsoleMessage]:
  595. return self.expect_event(Page.Events.Console, predicate, timeout)
  596. def expect_page(
  597. self,
  598. predicate: Callable[[Page], bool] = None,
  599. timeout: float = None,
  600. ) -> EventContextManagerImpl[Page]:
  601. return self.expect_event(BrowserContext.Events.Page, predicate, timeout)
  602. def _on_service_worker(self, worker: Worker) -> None:
  603. worker._context = self
  604. self._service_workers.add(worker)
  605. self.emit(BrowserContext.Events.ServiceWorker, worker)
  606. def _on_request_failed(
  607. self,
  608. request: Request,
  609. response_end_timing: float,
  610. failure_text: Optional[str],
  611. page: Optional[Page],
  612. ) -> None:
  613. request._failure_text = failure_text
  614. request._set_response_end_timing(response_end_timing)
  615. self.emit(BrowserContext.Events.RequestFailed, request)
  616. if page:
  617. page.emit(Page.Events.RequestFailed, request)
  618. def _on_request_finished(
  619. self,
  620. request: Request,
  621. response: Optional[Response],
  622. response_end_timing: float,
  623. page: Optional[Page],
  624. ) -> None:
  625. request._set_response_end_timing(response_end_timing)
  626. self.emit(BrowserContext.Events.RequestFinished, request)
  627. if page:
  628. page.emit(Page.Events.RequestFinished, request)
  629. if response:
  630. response._finished_future.set_result(True)
  631. def _on_console_message(self, event: Dict) -> None:
  632. message = ConsoleMessage(event, self._loop, self._dispatcher_fiber)
  633. worker = message.worker
  634. if worker:
  635. worker.emit(Worker.Events.Console, message)
  636. page = message.page
  637. if page:
  638. page.emit(Page.Events.Console, message)
  639. self.emit(BrowserContext.Events.Console, message)
  640. def _on_dialog(self, dialog: Dialog) -> None:
  641. has_listeners = self.emit(BrowserContext.Events.Dialog, dialog)
  642. page = dialog.page
  643. if page:
  644. has_listeners = page.emit(Page.Events.Dialog, dialog) or has_listeners
  645. if not has_listeners:
  646. # Although we do similar handling on the server side, we still need this logic
  647. # on the client side due to a possible race condition between two async calls:
  648. # a) removing "dialog" listener subscription (client->server)
  649. # b) actual "dialog" event (server->client)
  650. if dialog.type == "beforeunload":
  651. asyncio.create_task(dialog.accept())
  652. else:
  653. asyncio.create_task(dialog.dismiss())
  654. def _on_page_error(self, error: Error, page: Optional[Page]) -> None:
  655. self.emit(
  656. BrowserContext.Events.WebError,
  657. WebError(self._loop, self._dispatcher_fiber, page, error),
  658. )
  659. if page:
  660. page.emit(Page.Events.PageError, error)
  661. def _on_request(self, request: Request, page: Optional[Page]) -> None:
  662. self.emit(BrowserContext.Events.Request, request)
  663. if page:
  664. page.emit(Page.Events.Request, request)
  665. def _on_response(self, response: Response, page: Optional[Page]) -> None:
  666. self.emit(BrowserContext.Events.Response, response)
  667. if page:
  668. page.emit(Page.Events.Response, response)
  669. @property
  670. def background_pages(self) -> List[Page]:
  671. return []
  672. @property
  673. def service_workers(self) -> List[Worker]:
  674. return list(self._service_workers)
  675. async def new_cdp_session(self, page: Union[Page, Frame]) -> CDPSession:
  676. page = to_impl(page)
  677. params = {}
  678. if isinstance(page, Page):
  679. params["page"] = page._channel
  680. elif isinstance(page, Frame):
  681. params["frame"] = page._channel
  682. else:
  683. raise Error("page: expected Page or Frame")
  684. return from_channel(await self._channel.send("newCDPSession", None, params))
  685. @property
  686. def tracing(self) -> Tracing:
  687. return self._tracing
  688. @property
  689. def request(self) -> "APIRequestContext":
  690. return self._request
  691. @property
  692. def clock(self) -> Clock:
  693. return self._clock