1use crate::browser::Browser;
5use crate::api_request::APIRequestContext;
6use crate::error::{Error, Result};
7use crate::options::NewContextOptions;
8use crate::page::Page;
9use crate::route::RouteEntry;
10use crate::route::{Route, RouteHandler};
11use crate::types::Headers;
12use parking_lot::Mutex;
13use serde_json::{json, Value};
14use std::future::Future;
15use std::pin::Pin;
16use std::sync::atomic::{AtomicU64, Ordering};
17use std::sync::Arc;
18
19type PageHandler = Arc<
20 dyn Fn(Page) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync,
21>;
22
23#[derive(Clone)]
26pub struct BrowserContext {
27 inner: Arc<ContextInner>,
28}
29
30struct ContextInner {
31 browser: Browser,
32 browser_context_id: Option<String>,
33 init_scripts: Mutex<Vec<String>>,
34 default_timeout_ms: AtomicU64,
35 extra_http_headers: Mutex<Option<Headers>>,
36 user_agent: Mutex<Option<String>>,
37 viewport: Mutex<Option<crate::types::Viewport>>,
38 pages: Mutex<Vec<Page>>,
39 on_page_handlers: Mutex<Vec<PageHandler>>,
40 context_routes: Mutex<Vec<RouteEntry>>,
43 utility_page: Mutex<Option<Page>>,
44 tracing: crate::Tracing,
47 closed: Mutex<bool>,
48}
49
50impl BrowserContext {
51 pub(crate) async fn create(browser: Browser, opts: Option<NewContextOptions>) -> Result<Self> {
53 let v = browser
54 .browser_session()
55 .send(
56 "Target.createBrowserContext",
57 json!({"disposeOnDetach": true}),
58 )
59 .await?;
60 let bcid = v
61 .get("browserContextId")
62 .and_then(|x| x.as_str())
63 .ok_or_else(|| Error::ProtocolError("createBrowserContext missing id".into()))?
64 .to_string();
65
66 let ctx = Self::build(browser.clone(), Some(bcid), opts);
67 browser.track_context(ctx.clone());
68 Ok(ctx)
69 }
70
71 pub(crate) fn default_for(browser: Browser) -> Self {
73 Self::build(browser, None, None)
74 }
75
76 fn build(browser: Browser, bcid: Option<String>, opts: Option<NewContextOptions>) -> Self {
77 let opts = opts.unwrap_or_default();
78 let tracing = crate::Tracing::new(browser.new_browser_cdp_session());
79 Self {
80 inner: Arc::new(ContextInner {
81 browser,
82 browser_context_id: bcid,
83 init_scripts: Mutex::new(Vec::new()),
84 default_timeout_ms: AtomicU64::new(30_000),
85 extra_http_headers: Mutex::new(opts.extra_http_headers),
86 user_agent: Mutex::new(opts.user_agent),
87 viewport: Mutex::new(opts.viewport),
88 pages: Mutex::new(Vec::new()),
89 on_page_handlers: Mutex::new(Vec::new()),
90 context_routes: Mutex::new(Vec::new()),
91 utility_page: Mutex::new(None),
92 tracing,
93 closed: Mutex::new(false),
94 }),
95 }
96 }
97
98 pub fn browser_context_id(&self) -> Option<&str> {
100 self.inner.browser_context_id.as_deref()
101 }
102
103 pub async fn new_page(&self) -> Result<Page> {
105 if *self.inner.closed.lock() {
106 return Err(Error::TargetClosed {
107 target_type: "context".into(),
108 context: "context already closed".into(),
109 });
110 }
111
112 let mut params = json!({"url": "about:blank"});
113 if let Some(id) = &self.inner.browser_context_id {
114 params["browserContextId"] = json!(id);
115 }
116 let resp = self
117 .inner
118 .browser
119 .browser_session()
120 .send("Target.createTarget", params)
121 .await?;
122 let target_id = resp
123 .get("targetId")
124 .and_then(|x| x.as_str())
125 .ok_or_else(|| Error::ProtocolError("createTarget missing targetId".into()))?
126 .to_string();
127
128 let attach = self
129 .inner
130 .browser
131 .browser_session()
132 .send(
133 "Target.attachToTarget",
134 json!({"targetId": target_id, "flatten": true}),
135 )
136 .await?;
137 let session_id = attach
138 .get("sessionId")
139 .and_then(|x| x.as_str())
140 .ok_or_else(|| Error::ProtocolError("attachToTarget missing sessionId".into()))?
141 .to_string();
142
143 let init_scripts = self.inner.init_scripts.lock().clone();
144 let headers = self.inner.extra_http_headers.lock().clone();
145 let ua = self.inner.user_agent.lock().clone();
146 let viewport = self.inner.viewport.lock().as_ref().copied();
147 let timeout_ms = self.inner.default_timeout_ms.load(Ordering::Relaxed);
148
149 let page = Page::attach(
150 self.inner.browser.clone(),
151 session_id,
152 target_id,
153 &init_scripts,
154 timeout_ms,
155 headers.as_ref(),
156 ua.as_deref(),
157 viewport,
158 )
159 .await?;
160
161 self.inner.pages.lock().push(page.clone());
162
163 let handlers = self.inner.on_page_handlers.lock().clone();
165 for h in handlers {
166 let p = page.clone();
167 tokio::spawn(async move {
168 (h)(p).await;
169 });
170 }
171
172 let routes = self.inner.context_routes.lock().clone();
175 for entry in routes {
176 let handler = Arc::clone(&entry.handler);
177 page.route(&entry.pattern, move |r| {
178 let h = Arc::clone(&handler);
179 async move {
180 (h)(r).await;
181 }
182 })
183 .await?;
184 }
185
186 Ok(page)
187 }
188
189 pub async fn add_init_script(&self, script: &str) -> Result<()> {
191 self.inner.init_scripts.lock().push(script.to_string());
192 let pages = self.inner.pages.lock().clone();
194 for p in pages {
195 p.add_init_script(script).await?;
196 }
197 Ok(())
198 }
199
200 pub fn set_default_timeout(&self, timeout_ms: u64) {
202 self.inner.default_timeout_ms.store(timeout_ms, Ordering::Relaxed);
203 for p in self.inner.pages.lock().iter() {
204 p.set_default_timeout(timeout_ms);
205 }
206 }
207
208 pub async fn set_extra_http_headers(&self, headers: Headers) -> Result<()> {
210 *self.inner.extra_http_headers.lock() = Some(headers.clone());
211 let pages = self.inner.pages.lock().clone();
212 for p in pages {
213 p.set_extra_http_headers(headers.clone()).await?;
214 }
215 Ok(())
216 }
217
218 pub fn request(&self) -> APIRequestContext {
223 let headers = self
224 .inner
225 .extra_http_headers
226 .lock()
227 .clone()
228 .unwrap_or_default();
229 APIRequestContext::new(headers)
230 }
231
232 pub fn pages(&self) -> Vec<Page> {
234 self.inner.pages.lock().clone()
235 }
236
237 pub fn browser(&self) -> Browser {
239 self.inner.browser.clone()
240 }
241
242 #[allow(dead_code)]
247 async fn ensure_utility_page(&self) -> Result<Page> {
248 if let Some(p) = self.inner.utility_page.lock().clone() {
249 return Ok(p);
250 }
251 let page = self.new_page().await?;
253 *self.inner.utility_page.lock() = Some(page.clone());
254 Ok(page)
255 }
256
257 pub async fn add_cookies(&self, cookies: &[crate::types::Cookie]) -> Result<()> {
259 let mut params = json!({ "cookies": serde_json::to_value(cookies)? });
262 if let Some(id) = &self.inner.browser_context_id {
263 params["browserContextId"] = json!(id);
264 }
265 self.inner
266 .browser
267 .browser_session()
268 .send("Storage.setCookies", params)
269 .await?;
270 Ok(())
271 }
272
273 pub async fn cookies(&self) -> Result<Vec<Value>> {
275 let mut params = json!({});
276 if let Some(id) = &self.inner.browser_context_id {
277 params["browserContextId"] = json!(id);
278 }
279 let v = self
280 .inner
281 .browser
282 .browser_session()
283 .send("Storage.getCookies", params)
284 .await?;
285 let cookies = v
286 .get("cookies")
287 .cloned()
288 .unwrap_or_else(|| Value::Array(vec![]));
289 Ok(cookies
290 .as_array()
291 .cloned()
292 .unwrap_or_default())
293 }
294
295 pub async fn clear_cookies(&self) -> Result<()> {
297 let mut params = json!({});
298 if let Some(id) = &self.inner.browser_context_id {
299 params["browserContextId"] = json!(id);
300 }
301 self.inner
302 .browser
303 .browser_session()
304 .send("Storage.clearCookies", params)
305 .await?;
306 Ok(())
307 }
308
309 pub fn on_page<F, Fut>(&self, handler: F)
313 where
314 F: Fn(Page) -> Fut + Send + Sync + 'static,
315 Fut: Future<Output = ()> + Send + 'static,
316 {
317 let h: PageHandler = Arc::new(move |p| Box::pin(handler(p)));
318 self.inner.on_page_handlers.lock().push(h);
319 }
320
321 pub async fn close(&self) -> Result<()> {
323 if *self.inner.closed.lock() {
324 return Ok(());
325 }
326 *self.inner.closed.lock() = true;
327
328 let pages = std::mem::take(&mut *self.inner.pages.lock());
330 for p in pages {
331 let _ = p.close().await;
332 }
333 if let Some(_util) = self.inner.utility_page.lock().take() {
334 }
336
337 if let Some(bcid) = &self.inner.browser_context_id {
338 let _ = self
339 .inner
340 .browser
341 .browser_session()
342 .send("Target.disposeBrowserContext", json!({"browserContextId": bcid}))
343 .await;
344 }
345 Ok(())
346 }
347
348 pub async fn grant_permissions(&self, permissions: &[&str]) -> Result<()> {
353 self.inner
354 .browser
355 .browser_session()
356 .send(
357 "Browser.grantPermissions",
358 json!({ "permissions": permissions }),
359 )
360 .await
361 .map(|_: Value| ())
362 }
363
364 pub async fn clear_permissions(&self) -> Result<()> {
366 self.inner
367 .browser
368 .browser_session()
369 .send("Browser.resetPermissions", json!({}))
370 .await
371 .map(|_: Value| ())
372 }
373
374 pub async fn storage_state(&self) -> Result<crate::types::StorageState> {
380 let cookies = self.cookies().await?;
381
382 let mut origins: Vec<crate::types::OriginStorage> = Vec::new();
384 for page in self.pages() {
385 let v: Value = page
387 .evaluate(
388 "JSON.stringify({ origin: location.origin, entries: Object.entries(localStorage).map(([n,v]) => ({name:n, value:v})) })",
389 )
390 .await?;
391 let parsed: Value = match &v {
393 Value::String(s) => serde_json::from_str(s).unwrap_or(Value::Null),
394 other => other.clone(),
395 };
396 let origin = parsed
397 .get("origin")
398 .and_then(|o| o.as_str())
399 .unwrap_or("")
400 .to_string();
401 if origin.is_empty() || origin == "null" {
403 continue;
404 }
405 let entries = parsed
406 .get("entries")
407 .and_then(|e| e.as_array())
408 .cloned()
409 .unwrap_or_default();
410 let local_storage: Vec<crate::types::NameValue> = entries
411 .into_iter()
412 .filter_map(|e| {
413 Some(crate::types::NameValue {
414 name: e.get("name")?.as_str()?.to_string(),
415 value: e.get("value")?.as_str()?.to_string(),
416 })
417 })
418 .collect();
419
420 if let Some(existing) = origins.iter_mut().find(|o| o.origin == origin) {
421 for nv in local_storage {
423 if let Some(pos) = existing.localStorage.iter().position(|x| x.name == nv.name) {
424 existing.localStorage[pos].value = nv.value;
425 } else {
426 existing.localStorage.push(nv);
427 }
428 }
429 } else {
430 origins.push(crate::types::OriginStorage {
431 origin,
432 localStorage: local_storage,
433 });
434 }
435 }
436
437 Ok(crate::types::StorageState { cookies, origins })
438 }
439
440 pub async fn set_storage_state(
445 &self,
446 state: &crate::types::StorageState,
447 ) -> Result<()> {
448 if !state.cookies.is_empty() {
450 let mut params = json!({ "cookies": state.cookies });
454 if let Some(id) = &self.inner.browser_context_id {
455 params["browserContextId"] = json!(id);
456 }
457 self.inner
458 .browser
459 .browser_session()
460 .send("Storage.setCookies", params)
461 .await?;
462 }
463
464 for origin_state in &state.origins {
466 if origin_state.localStorage.is_empty() {
467 continue;
468 }
469 let mut target: Option<Page> = None;
471 for p in self.pages() {
472 let p_origin: String = match p.evaluate::<String>("location.origin").await {
473 Ok(o) => o,
474 Err(_) => continue,
475 };
476 if p_origin == origin_state.origin {
477 target = Some(p);
478 break;
479 }
480 }
481 let page = match target {
482 Some(p) => p,
483 None => {
484 let p = self.new_page().await?;
485 let url = format!("{}/", origin_state.origin);
486 p.goto(&url, None).await?;
487 p
488 }
489 };
490
491 let _: Value = page
493 .evaluate_with_arg(
494 "(() => { for (const {name, value} of arg) localStorage.setItem(name, value); return null; })()",
495 &origin_state.localStorage,
496 )
497 .await?;
498 }
499
500 Ok(())
501 }
502
503 pub async fn route<F, Fut>(&self, pattern: &str, handler: F) -> Result<()>
513 where
514 F: Fn(Route) -> Fut + Send + Sync + 'static,
515 Fut: Future<Output = ()> + Send + 'static,
516 {
517 let handler: RouteHandler = Arc::new(move |r| Box::pin(handler(r)));
518 let entry = RouteEntry {
519 pattern: pattern.to_string(),
520 handler: Arc::clone(&handler),
521 };
522 self.inner.context_routes.lock().push(entry);
523
524 let pat = pattern.to_string();
526 for page in self.pages() {
527 let h = Arc::clone(&handler);
528 page.route(&pat, move |r| {
529 let h = Arc::clone(&h);
530 async move {
531 (h)(r).await;
532 }
533 })
534 .await?;
535 }
536 Ok(())
537 }
538
539 pub async fn unroute(&self, pattern: &str) -> Result<()> {
542 let mut routes = self.inner.context_routes.lock();
543 if let Some(pos) = routes.iter().position(|e| e.pattern == pattern) {
544 routes.remove(pos);
545 }
546 drop(routes);
547
548 for page in self.pages() {
549 page.unroute(pattern).await?;
550 }
551 Ok(())
552 }
553
554 pub async fn unroute_all(&self) -> Result<()> {
556 self.inner.context_routes.lock().clear();
557 for page in self.pages() {
558 page.unroute_all().await?;
559 }
560 Ok(())
561 }
562
563 pub fn tracing(&self) -> crate::Tracing {
569 self.inner.tracing.clone()
570 }
571}