chromiumoxide/page.rs
1use std::path::Path;
2use std::sync::Arc;
3
4use chromiumoxide_cdp::cdp::browser_protocol::emulation::{
5 MediaFeature, SetDeviceMetricsOverrideParams, SetEmulatedMediaParams,
6 SetGeolocationOverrideParams, SetLocaleOverrideParams, SetTimezoneOverrideParams,
7 UserAgentBrandVersion, UserAgentMetadata,
8};
9use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchDragEventType, DragData};
10use chromiumoxide_cdp::cdp::browser_protocol::network::{
11 Cookie, CookieParam, DeleteCookiesParams, GetCookiesParams, SetBlockedUrLsParams,
12 SetCookiesParams, SetExtraHttpHeadersParams, SetUserAgentOverrideParams,
13};
14use chromiumoxide_cdp::cdp::browser_protocol::page::*;
15use chromiumoxide_cdp::cdp::browser_protocol::performance::{GetMetricsParams, Metric};
16use chromiumoxide_cdp::cdp::browser_protocol::target::{SessionId, TargetId};
17use chromiumoxide_cdp::cdp::browser_protocol::{dom::*, emulation};
18use chromiumoxide_cdp::cdp::js_protocol;
19use chromiumoxide_cdp::cdp::js_protocol::debugger::GetScriptSourceParams;
20use chromiumoxide_cdp::cdp::js_protocol::runtime::{
21 AddBindingParams, CallArgument, CallFunctionOnParams, EvaluateParams, ExecutionContextId,
22 RemoteObjectType, ScriptId,
23};
24use chromiumoxide_cdp::cdp::{browser_protocol, IntoEventKind};
25use chromiumoxide_types::*;
26use futures::channel::mpsc::unbounded;
27use futures::channel::oneshot::channel as oneshot_channel;
28use futures::{stream, SinkExt, StreamExt};
29use spider_fingerprint::configs::{AgentOs, Tier};
30
31use crate::auth::Credentials;
32use crate::element::Element;
33use crate::error::{CdpError, Result};
34use crate::handler::commandfuture::CommandFuture;
35use crate::handler::domworld::DOMWorldKind;
36use crate::handler::httpfuture::HttpFuture;
37use crate::handler::target::{GetName, GetParent, GetUrl, TargetMessage};
38use crate::handler::PageInner;
39use crate::javascript::extract::{generate_marker_js, FULL_XML_SERIALIZER_JS, OUTER_HTML};
40use crate::js::{Evaluation, EvaluationResult};
41use crate::layout::{Delta, Point, ScrollBehavior};
42use crate::listeners::{EventListenerRequest, EventStream};
43use crate::{utils, ArcHttpRequest};
44use aho_corasick::AhoCorasick;
45
46lazy_static::lazy_static! {
47 /// Determine the platform used.
48 static ref PLATFORM_MATCHER: AhoCorasick = {
49 AhoCorasick::builder()
50 .match_kind(aho_corasick::MatchKind::LeftmostFirst)
51 .ascii_case_insensitive(true)
52 .build([
53 "ipad", // 0
54 "ipod", // 1
55 "iphone", // 2
56 "android", // 3
57 "macintosh", // 4
58 "mac os x", // 5
59 "windows", // 6
60 "linux", // 7
61 ])
62 .expect("valid pattern")
63 };
64}
65
66/// Determine the platform used from a user-agent.
67pub fn platform_from_user_agent(user_agent: &str) -> &'static str {
68 match PLATFORM_MATCHER.find(user_agent) {
69 Some(mat) => match mat.pattern().as_usize() {
70 0 => "iPad",
71 1 => "iPod",
72 2 => "iPhone",
73 3 => "Linux armv8l",
74 4 | 5 => "MacIntel",
75 6 => "Win32",
76 7 => "Linux x86_64",
77 _ => "",
78 },
79 None => "",
80 }
81}
82
83#[derive(Debug, Clone)]
84pub struct Page {
85 inner: Arc<PageInner>,
86}
87
88impl Page {
89 /// Add a custom script to eval on new document immediately.
90 pub async fn add_script_to_evaluate_immediately_on_new_document(
91 &self,
92 source: Option<String>,
93 ) -> Result<()> {
94 if source.is_some() {
95 let source = source.unwrap_or_default();
96
97 if !source.is_empty() {
98 self.execute(AddScriptToEvaluateOnNewDocumentParams {
99 source,
100 world_name: None,
101 include_command_line_api: None,
102 run_immediately: Some(true),
103 })
104 .await?;
105 }
106 }
107 Ok(())
108 }
109
110 /// Add a custom script to eval on new document.
111 pub async fn add_script_to_evaluate_on_new_document(
112 &self,
113 source: Option<String>,
114 ) -> Result<()> {
115 if source.is_some() {
116 let source = source.unwrap_or_default();
117
118 if !source.is_empty() {
119 self.execute(AddScriptToEvaluateOnNewDocumentParams {
120 source,
121 world_name: None,
122 include_command_line_api: None,
123 run_immediately: None,
124 })
125 .await?;
126 }
127 }
128 Ok(())
129 }
130
131 /// Removes the `navigator.webdriver` property
132 /// changes permissions, pluggins rendering contexts and the `window.chrome`
133 /// property to make it harder to detect the scraper as a bot.
134 pub async fn _enable_real_emulation(
135 &self,
136 user_agent: &str,
137 config: &spider_fingerprint::EmulationConfiguration,
138 viewport: &Option<&spider_fingerprint::spoof_viewport::Viewport>,
139 custom_script: Option<&str>,
140 ) -> Result<()> {
141 let emulation_script = spider_fingerprint::emulate(
142 &user_agent,
143 &config,
144 &viewport,
145 &custom_script.as_ref().map(|s| Box::new(s.to_string())),
146 )
147 .unwrap_or_default();
148
149 let source = if let Some(cs) = custom_script {
150 format!(
151 "{};{};",
152 emulation_script,
153 spider_fingerprint::wrap_eval_script(&cs)
154 )
155 } else {
156 emulation_script
157 };
158
159 self.add_script_to_evaluate_on_new_document(Some(source))
160 .await?;
161
162 Ok(())
163 }
164
165 /// Removes the `navigator.webdriver` property
166 /// changes permissions, pluggins rendering contexts and the `window.chrome`
167 /// property to make it harder to detect the scraper as a bot
168 pub async fn _enable_stealth_mode(
169 &self,
170 custom_script: Option<&str>,
171 os: Option<AgentOs>,
172 tier: Option<Tier>,
173 ) -> Result<()> {
174 let os = os.unwrap_or_default();
175 let tier = match tier {
176 Some(tier) => tier,
177 _ => Tier::Basic,
178 };
179
180 let source = if let Some(cs) = custom_script {
181 format!(
182 "{};{};",
183 spider_fingerprint::build_stealth_script(tier, os),
184 spider_fingerprint::wrap_eval_script(&cs)
185 )
186 } else {
187 spider_fingerprint::build_stealth_script(tier, os)
188 };
189
190 self.add_script_to_evaluate_on_new_document(Some(source))
191 .await?;
192
193 Ok(())
194 }
195
196 /// Changes your user_agent, removes the `navigator.webdriver` property
197 /// changes permissions, pluggins rendering contexts and the `window.chrome`
198 /// property to make it harder to detect the scraper as a bot
199 pub async fn enable_stealth_mode(&self) -> Result<()> {
200 let _ = self._enable_stealth_mode(None, None, None).await;
201
202 Ok(())
203 }
204
205 /// Changes your user_agent, removes the `navigator.webdriver` property
206 /// changes permissions, pluggins rendering contexts and the `window.chrome`
207 /// property to make it harder to detect the scraper as a bot
208 pub async fn enable_stealth_mode_os(
209 &self,
210 os: Option<AgentOs>,
211 tier: Option<Tier>,
212 ) -> Result<()> {
213 let _ = self._enable_stealth_mode(None, os, tier).await;
214
215 Ok(())
216 }
217
218 /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
219 /// changes permissions, pluggins rendering contexts and the `window.chrome`
220 /// property to make it harder to detect the scraper as a bot
221 pub async fn enable_stealth_mode_with_agent(&self, ua: &str) -> Result<()> {
222 let _ = tokio::join!(
223 self._enable_stealth_mode(None, None, None),
224 self.set_user_agent(ua)
225 );
226 Ok(())
227 }
228
229 /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
230 /// changes permissions, pluggins rendering contexts and the `window.chrome`
231 /// property to make it harder to detect the scraper as a bot. Also add dialog polyfill to prevent blocking the page.
232 pub async fn enable_stealth_mode_with_dimiss_dialogs(&self, ua: &str) -> Result<()> {
233 let _ = tokio::join!(
234 self._enable_stealth_mode(
235 Some(spider_fingerprint::spoofs::DISABLE_DIALOGS),
236 None,
237 None
238 ),
239 self.set_user_agent(ua)
240 );
241 Ok(())
242 }
243
244 /// Changes your user_agent with a custom agent, removes the `navigator.webdriver` property
245 /// changes permissions, pluggins rendering contexts and the `window.chrome`
246 /// property to make it harder to detect the scraper as a bot. Also add dialog polyfill to prevent blocking the page.
247 pub async fn enable_stealth_mode_with_agent_and_dimiss_dialogs(&self, ua: &str) -> Result<()> {
248 let _ = tokio::join!(
249 self._enable_stealth_mode(
250 Some(spider_fingerprint::spoofs::DISABLE_DIALOGS),
251 None,
252 None
253 ),
254 self.set_user_agent(ua)
255 );
256 Ok(())
257 }
258
259 /// Sets `window.chrome` on frame creation and console.log methods.
260 pub async fn hide_chrome(&self) -> Result<(), CdpError> {
261 self.execute(AddScriptToEvaluateOnNewDocumentParams {
262 source: spider_fingerprint::spoofs::HIDE_CHROME.to_string(),
263 world_name: None,
264 include_command_line_api: None,
265 run_immediately: None,
266 })
267 .await?;
268 Ok(())
269 }
270
271 /// Obfuscates WebGL vendor on frame creation
272 pub async fn hide_webgl_vendor(&self) -> Result<(), CdpError> {
273 self.execute(AddScriptToEvaluateOnNewDocumentParams {
274 source: spider_fingerprint::spoofs::HIDE_WEBGL.to_string(),
275 world_name: None,
276 include_command_line_api: None,
277 run_immediately: None,
278 })
279 .await?;
280 Ok(())
281 }
282
283 /// Obfuscates browser plugins and hides the navigator object on frame creation
284 pub async fn hide_plugins(&self) -> Result<(), CdpError> {
285 self.execute(AddScriptToEvaluateOnNewDocumentParams {
286 source: spider_fingerprint::generate_hide_plugins(),
287 world_name: None,
288 include_command_line_api: None,
289 run_immediately: None,
290 })
291 .await?;
292
293 Ok(())
294 }
295
296 /// Obfuscates browser permissions on frame creation
297 pub async fn hide_permissions(&self) -> Result<(), CdpError> {
298 self.execute(AddScriptToEvaluateOnNewDocumentParams {
299 source: spider_fingerprint::spoofs::HIDE_PERMISSIONS.to_string(),
300 world_name: None,
301 include_command_line_api: None,
302 run_immediately: None,
303 })
304 .await?;
305 Ok(())
306 }
307
308 /// Removes the `navigator.webdriver` property on frame creation
309 pub async fn hide_webdriver(&self) -> Result<(), CdpError> {
310 self.execute(AddScriptToEvaluateOnNewDocumentParams {
311 source: spider_fingerprint::spoofs::HIDE_WEBDRIVER.to_string(),
312 world_name: None,
313 include_command_line_api: None,
314 run_immediately: None,
315 })
316 .await?;
317 Ok(())
318 }
319
320 /// Execute a command and return the `Command::Response`
321 pub async fn execute<T: Command>(&self, cmd: T) -> Result<CommandResponse<T::Response>> {
322 self.command_future(cmd)?.await
323 }
324
325 /// Execute a command and return the `Command::Response`
326 pub fn command_future<T: Command>(&self, cmd: T) -> Result<CommandFuture<T>> {
327 self.inner.command_future(cmd)
328 }
329
330 /// Execute a command and return the `Command::Response`
331 pub fn http_future<T: Command>(&self, cmd: T) -> Result<HttpFuture<T>> {
332 self.inner.http_future(cmd)
333 }
334
335 /// Adds an event listener to the `Target` and returns the receiver part as
336 /// `EventStream`
337 ///
338 /// An `EventStream` receives every `Event` the `Target` receives.
339 /// All event listener get notified with the same event, so registering
340 /// multiple listeners for the same event is possible.
341 ///
342 /// Custom events rely on being deserializable from the received json params
343 /// in the `EventMessage`. Custom Events are caught by the `CdpEvent::Other`
344 /// variant. If there are mulitple custom event listener is registered
345 /// for the same event, identified by the `MethodType::method_id` function,
346 /// the `Target` tries to deserialize the json using the type of the event
347 /// listener. Upon success the `Target` then notifies all listeners with the
348 /// deserialized event. This means, while it is possible to register
349 /// different types for the same custom event, only the type of first
350 /// registered event listener will be used. The subsequent listeners, that
351 /// registered for the same event but with another type won't be able to
352 /// receive anything and therefor will come up empty until all their
353 /// preceding event listeners are dropped and they become the first (or
354 /// longest) registered event listener for an event.
355 ///
356 /// # Example Listen for canceled animations
357 /// ```no_run
358 /// # use chromiumoxide::page::Page;
359 /// # use chromiumoxide::error::Result;
360 /// # use chromiumoxide_cdp::cdp::browser_protocol::animation::EventAnimationCanceled;
361 /// # use futures::StreamExt;
362 /// # async fn demo(page: Page) -> Result<()> {
363 /// let mut events = page.event_listener::<EventAnimationCanceled>().await?;
364 /// while let Some(event) = events.next().await {
365 /// //..
366 /// }
367 /// # Ok(())
368 /// # }
369 /// ```
370 ///
371 /// # Example Liste for a custom event
372 ///
373 /// ```no_run
374 /// # use chromiumoxide::page::Page;
375 /// # use chromiumoxide::error::Result;
376 /// # use futures::StreamExt;
377 /// # use serde::Deserialize;
378 /// # use chromiumoxide::types::{MethodId, MethodType};
379 /// # use chromiumoxide::cdp::CustomEvent;
380 /// # async fn demo(page: Page) -> Result<()> {
381 /// #[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
382 /// struct MyCustomEvent {
383 /// name: String,
384 /// }
385 /// impl MethodType for MyCustomEvent {
386 /// fn method_id() -> MethodId {
387 /// "Custom.Event".into()
388 /// }
389 /// }
390 /// impl CustomEvent for MyCustomEvent {}
391 /// let mut events = page.event_listener::<MyCustomEvent>().await?;
392 /// while let Some(event) = events.next().await {
393 /// //..
394 /// }
395 ///
396 /// # Ok(())
397 /// # }
398 /// ```
399 pub async fn event_listener<T: IntoEventKind>(&self) -> Result<EventStream<T>> {
400 let (tx, rx) = unbounded();
401
402 self.inner
403 .sender()
404 .clone()
405 .send(TargetMessage::AddEventListener(
406 EventListenerRequest::new::<T>(tx),
407 ))
408 .await?;
409
410 Ok(EventStream::new(rx))
411 }
412
413 pub async fn expose_function(
414 &self,
415 name: impl Into<String>,
416 function: impl AsRef<str>,
417 ) -> Result<()> {
418 let name = name.into();
419 let expression = utils::evaluation_string(function, &["exposedFun", name.as_str()]);
420
421 self.execute(AddBindingParams::new(name)).await?;
422 self.execute(AddScriptToEvaluateOnNewDocumentParams::new(
423 expression.clone(),
424 ))
425 .await?;
426
427 // TODO add execution context tracking for frames
428 //let frames = self.frames().await?;
429
430 Ok(())
431 }
432
433 /// This resolves once the navigation finished and the page is loaded.
434 ///
435 /// This is necessary after an interaction with the page that may trigger a
436 /// navigation (`click`, `press_key`) in order to wait until the new browser
437 /// page is loaded
438 pub async fn wait_for_navigation_response(&self) -> Result<ArcHttpRequest> {
439 self.inner.wait_for_navigation().await
440 }
441
442 /// Same as `wait_for_navigation_response` but returns `Self` instead
443 pub async fn wait_for_navigation(&self) -> Result<&Self> {
444 self.inner.wait_for_navigation().await?;
445 Ok(self)
446 }
447
448 /// Navigate directly to the given URL.
449 ///
450 /// This resolves directly after the requested URL is fully loaded.
451 pub async fn goto(&self, params: impl Into<NavigateParams>) -> Result<&Self> {
452 let res = self.execute(params.into()).await?;
453
454 if let Some(err) = res.result.error_text {
455 return Err(CdpError::ChromeMessage(err));
456 }
457
458 Ok(self)
459 }
460
461 /// The identifier of the `Target` this page belongs to
462 pub fn target_id(&self) -> &TargetId {
463 self.inner.target_id()
464 }
465
466 /// The identifier of the `Session` target of this page is attached to
467 pub fn session_id(&self) -> &SessionId {
468 self.inner.session_id()
469 }
470
471 /// The identifier of the `Session` target of this page is attached to
472 pub fn opener_id(&self) -> &Option<TargetId> {
473 self.inner.opener_id()
474 }
475
476 /// Returns the name of the frame
477 pub async fn frame_name(&self, frame_id: FrameId) -> Result<Option<String>> {
478 let (tx, rx) = oneshot_channel();
479 self.inner
480 .sender()
481 .clone()
482 .send(TargetMessage::Name(GetName {
483 frame_id: Some(frame_id),
484 tx,
485 }))
486 .await?;
487 Ok(rx.await?)
488 }
489
490 pub async fn authenticate(&self, credentials: Credentials) -> Result<()> {
491 self.inner
492 .sender()
493 .clone()
494 .send(TargetMessage::Authenticate(credentials))
495 .await?;
496
497 Ok(())
498 }
499
500 /// Returns the current url of the page
501 pub async fn url(&self) -> Result<Option<String>> {
502 let (tx, rx) = oneshot_channel();
503 self.inner
504 .sender()
505 .clone()
506 .send(TargetMessage::Url(GetUrl::new(tx)))
507 .await?;
508 Ok(rx.await?)
509 }
510
511 /// Returns the current url of the frame
512 pub async fn frame_url(&self, frame_id: FrameId) -> Result<Option<String>> {
513 let (tx, rx) = oneshot_channel();
514 self.inner
515 .sender()
516 .clone()
517 .send(TargetMessage::Url(GetUrl {
518 frame_id: Some(frame_id),
519 tx,
520 }))
521 .await?;
522 Ok(rx.await?)
523 }
524
525 /// Returns the parent id of the frame
526 pub async fn frame_parent(&self, frame_id: FrameId) -> Result<Option<FrameId>> {
527 let (tx, rx) = oneshot_channel();
528 self.inner
529 .sender()
530 .clone()
531 .send(TargetMessage::Parent(GetParent { frame_id, tx }))
532 .await?;
533 Ok(rx.await?)
534 }
535
536 /// Return the main frame of the page
537 pub async fn mainframe(&self) -> Result<Option<FrameId>> {
538 let (tx, rx) = oneshot_channel();
539 self.inner
540 .sender()
541 .clone()
542 .send(TargetMessage::MainFrame(tx))
543 .await?;
544 Ok(rx.await?)
545 }
546
547 /// Return the frames of the page
548 pub async fn frames(&self) -> Result<Vec<FrameId>> {
549 let (tx, rx) = oneshot_channel();
550 self.inner
551 .sender()
552 .clone()
553 .send(TargetMessage::AllFrames(tx))
554 .await?;
555 Ok(rx.await?)
556 }
557
558 /// Allows overriding user agent with the given string.
559 pub async fn set_extra_headers(
560 &self,
561 params: impl Into<SetExtraHttpHeadersParams>,
562 ) -> Result<&Self> {
563 self.execute(params.into()).await?;
564 Ok(self)
565 }
566
567 /// Generate the user-agent metadata params
568 pub fn generate_user_agent_metadata(
569 default_params: &SetUserAgentOverrideParams,
570 ) -> Option<UserAgentMetadata> {
571 let ua_data = spider_fingerprint::spoof_user_agent::build_high_entropy_data(&Some(
572 &default_params.user_agent,
573 ));
574 let windows = ua_data.platform == "Windows";
575
576 let brands = ua_data
577 .full_version_list
578 .iter()
579 .map(|b| {
580 let b = b.clone();
581 UserAgentBrandVersion::new(b.brand, b.version)
582 })
583 .collect::<Vec<_>>();
584
585 let full_versions = ua_data
586 .full_version_list
587 .into_iter()
588 .map(|b| UserAgentBrandVersion::new(b.brand, b.version))
589 .collect::<Vec<_>>();
590
591 let user_agent_metadata_builder = emulation::UserAgentMetadata::builder()
592 .architecture(ua_data.architecture)
593 .bitness(ua_data.bitness)
594 .model(ua_data.model)
595 .platform_version(ua_data.platform_version)
596 .brands(brands)
597 .full_version_lists(full_versions)
598 .platform(ua_data.platform)
599 .mobile(ua_data.mobile);
600
601 let user_agent_metadata_builder = if windows {
602 user_agent_metadata_builder.wow64(ua_data.wow64_ness)
603 } else {
604 user_agent_metadata_builder
605 };
606
607 if let Ok(user_agent_metadata) = user_agent_metadata_builder.build() {
608 Some(user_agent_metadata)
609 } else {
610 None
611 }
612 }
613
614 /// Allows overriding the user-agent for the [network](https://chromedevtools.github.io/devtools-protocol/tot/Network/#method-setUserAgentOverride) and [emulation](https://chromedevtools.github.io/devtools-protocol/tot/Emulation/#method-setUserAgentOverride ) with the given string.
615 pub async fn set_user_agent(
616 &self,
617 params: impl Into<SetUserAgentOverrideParams>,
618 ) -> Result<&Self> {
619 let mut default_params: SetUserAgentOverrideParams = params.into();
620
621 if default_params.platform.is_none() {
622 let platform = platform_from_user_agent(&default_params.user_agent);
623 if !platform.is_empty() {
624 default_params.platform = Some(platform.into());
625 }
626 }
627
628 if default_params.user_agent_metadata.is_none() {
629 let user_agent_metadata = Self::generate_user_agent_metadata(&default_params);
630 if let Some(user_agent_metadata) = user_agent_metadata {
631 default_params.user_agent_metadata = Some(user_agent_metadata);
632 }
633 }
634
635 self.execute(default_params).await?;
636
637 Ok(self)
638 }
639
640 /// Returns the user agent of the browser
641 pub async fn user_agent(&self) -> Result<String> {
642 Ok(self.inner.version().await?.user_agent)
643 }
644
645 /// Returns the root DOM node (and optionally the subtree) of the page.
646 ///
647 /// # Note: This does not return the actual HTML document of the page. To
648 /// retrieve the HTML content of the page see `Page::content`.
649 pub async fn get_document(&self) -> Result<Node> {
650 let mut cmd = GetDocumentParams::default();
651 cmd.depth = Some(-1);
652 cmd.pierce = Some(true);
653
654 let resp = self.execute(cmd).await?;
655
656 Ok(resp.result.root)
657 }
658
659 /// Returns the first element in the document which matches the given CSS
660 /// selector.
661 ///
662 /// Execute a query selector on the document's node.
663 pub async fn find_element(&self, selector: impl Into<String>) -> Result<Element> {
664 let root = self.get_document().await?.node_id;
665 let node_id = self.inner.find_element(selector, root).await?;
666 Element::new(Arc::clone(&self.inner), node_id).await
667 }
668
669 /// Returns the outer HTML of the page.
670 pub async fn outer_html(&self) -> Result<String> {
671 let root = self.get_document().await?;
672 let element = Element::new(Arc::clone(&self.inner), root.node_id).await?;
673 self.inner
674 .outer_html(
675 element.remote_object_id,
676 element.node_id,
677 element.backend_node_id,
678 )
679 .await
680 }
681
682 /// Return all `Element`s in the document that match the given selector
683 pub async fn find_elements(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
684 let root = self.get_document().await?.node_id;
685 let node_ids = self.inner.find_elements(selector, root).await?;
686 Element::from_nodes(&self.inner, &node_ids).await
687 }
688
689 /// Returns the first element in the document which matches the given xpath
690 /// selector.
691 ///
692 /// Execute a xpath selector on the document's node.
693 pub async fn find_xpath(&self, selector: impl Into<String>) -> Result<Element> {
694 self.get_document().await?;
695 let node_id = self.inner.find_xpaths(selector).await?[0];
696 Element::new(Arc::clone(&self.inner), node_id).await
697 }
698
699 /// Return all `Element`s in the document that match the given xpath selector
700 pub async fn find_xpaths(&self, selector: impl Into<String>) -> Result<Vec<Element>> {
701 self.get_document().await?;
702 let node_ids = self.inner.find_xpaths(selector).await?;
703 Element::from_nodes(&self.inner, &node_ids).await
704 }
705
706 /// Describes node given its id
707 pub async fn describe_node(&self, node_id: NodeId) -> Result<Node> {
708 let resp = self
709 .execute(DescribeNodeParams::builder().node_id(node_id).build())
710 .await?;
711 Ok(resp.result.node)
712 }
713
714 /// Tries to close page, running its beforeunload hooks, if any.
715 /// Calls Page.close with [`CloseParams`]
716 pub async fn close(self) -> Result<()> {
717 self.execute(CloseParams::default()).await?;
718 Ok(())
719 }
720
721 /// Performs a single mouse click event at the point's location.
722 ///
723 /// This scrolls the point into view first, then executes a
724 /// `DispatchMouseEventParams` command of type `MouseLeft` with
725 /// `MousePressed` as single click and then releases the mouse with an
726 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
727 /// `MouseReleased`
728 ///
729 /// Bear in mind that if `click()` triggers a navigation the new page is not
730 /// immediately loaded when `click()` resolves. To wait until navigation is
731 /// finished an additional `wait_for_navigation()` is required:
732 ///
733 /// # Example
734 ///
735 /// Trigger a navigation and wait until the triggered navigation is finished
736 ///
737 /// ```no_run
738 /// # use chromiumoxide::page::Page;
739 /// # use chromiumoxide::error::Result;
740 /// # use chromiumoxide::layout::Point;
741 /// # async fn demo(page: Page, point: Point) -> Result<()> {
742 /// let html = page.click(point).await?.wait_for_navigation().await?.content();
743 /// # Ok(())
744 /// # }
745 /// ```
746 ///
747 /// # Example
748 ///
749 /// Perform custom click
750 ///
751 /// ```no_run
752 /// # use chromiumoxide::page::Page;
753 /// # use chromiumoxide::error::Result;
754 /// # use chromiumoxide::layout::Point;
755 /// # use chromiumoxide_cdp::cdp::browser_protocol::input::{DispatchMouseEventParams, MouseButton, DispatchMouseEventType};
756 /// # async fn demo(page: Page, point: Point) -> Result<()> {
757 /// // double click
758 /// let cmd = DispatchMouseEventParams::builder()
759 /// .x(point.x)
760 /// .y(point.y)
761 /// .button(MouseButton::Left)
762 /// .click_count(2);
763 ///
764 /// page.move_mouse(point).await?.execute(
765 /// cmd.clone()
766 /// .r#type(DispatchMouseEventType::MousePressed)
767 /// .build()
768 /// .unwrap(),
769 /// )
770 /// .await?;
771 ///
772 /// page.execute(
773 /// cmd.r#type(DispatchMouseEventType::MouseReleased)
774 /// .build()
775 /// .unwrap(),
776 /// )
777 /// .await?;
778 ///
779 /// # Ok(())
780 /// # }
781 /// ```
782 pub async fn click(&self, point: Point) -> Result<&Self> {
783 self.inner.click(point).await?;
784 Ok(self)
785 }
786
787 /// Performs a single mouse click event at the point's location and generate a marker.
788 pub(crate) async fn click_with_highlight_base(
789 &self,
790 point: Point,
791 color: Rgba,
792 ) -> Result<&Self> {
793 use chromiumoxide_cdp::cdp::browser_protocol::overlay::HighlightRectParams;
794 let x = point.x.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
795 let y = point.y.round().clamp(i64::MIN as f64, i64::MAX as f64) as i64;
796
797 let highlight_params = HighlightRectParams {
798 x,
799 y,
800 width: 15,
801 height: 15,
802 color: Some(color),
803 outline_color: Some(Rgba::new(255, 255, 255)),
804 };
805
806 let _ = tokio::join!(self.click(point), self.execute(highlight_params));
807 Ok(self)
808 }
809
810 /// Performs a single mouse click event at the point's location and generate a highlight to the nearest element.
811 /// Make sure page.enable_overlay is called first.
812 pub async fn click_with_highlight(&self, point: Point) -> Result<&Self> {
813 let mut color = Rgba::new(255, 0, 0);
814 color.a = Some(1.0);
815 self.click_with_highlight_base(point, color).await?;
816 Ok(self)
817 }
818
819 /// Performs a single mouse click event at the point's location and generate a highlight to the nearest element with the color.
820 /// Make sure page.enable_overlay is called first.
821 pub async fn click_with_highlight_color(&self, point: Point, color: Rgba) -> Result<&Self> {
822 self.click_with_highlight_base(point, color).await?;
823 Ok(self)
824 }
825
826 /// Performs a single mouse click event at the point's location and generate a marker with pure JS. Useful for debugging.
827 pub async fn click_with_marker(&self, point: Point) -> Result<&Self> {
828 let _ = tokio::join!(
829 self.click(point),
830 self.evaluate(generate_marker_js(point.x, point.y))
831 );
832
833 Ok(self)
834 }
835
836 /// Performs a double mouse click event at the point's location.
837 ///
838 /// This scrolls the point into view first, then executes a
839 /// `DispatchMouseEventParams` command of type `MouseLeft` with
840 /// `MousePressed` as single click and then releases the mouse with an
841 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
842 /// `MouseReleased`
843 ///
844 /// Bear in mind that if `click()` triggers a navigation the new page is not
845 /// immediately loaded when `click()` resolves. To wait until navigation is
846 /// finished an additional `wait_for_navigation()` is required:
847 ///
848 /// # Example
849 ///
850 /// Trigger a navigation and wait until the triggered navigation is finished
851 ///
852 /// ```no_run
853 /// # use chromiumoxide::page::Page;
854 /// # use chromiumoxide::error::Result;
855 /// # use chromiumoxide::layout::Point;
856 /// # async fn demo(page: Page, point: Point) -> Result<()> {
857 /// let html = page.click(point).await?.wait_for_navigation().await?.content();
858 /// # Ok(())
859 /// # }
860 /// ```
861 /// ```
862 pub async fn double_click(&self, point: Point) -> Result<&Self> {
863 self.inner.double_click(point).await?;
864 Ok(self)
865 }
866
867 /// Performs a single mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
868 ///
869 /// This scrolls the point into view first, then executes a
870 /// `DispatchMouseEventParams` command of type `MouseLeft` with
871 /// `MousePressed` as single click and then releases the mouse with an
872 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
873 /// `MouseReleased`
874 ///
875 /// Bear in mind that if `click()` triggers a navigation the new page is not
876 /// immediately loaded when `click()` resolves. To wait until navigation is
877 /// finished an additional `wait_for_navigation()` is required:
878 ///
879 /// # Example
880 ///
881 /// Trigger a navigation and wait until the triggered navigation is finished
882 ///
883 /// ```no_run
884 /// # use chromiumoxide::page::Page;
885 /// # use chromiumoxide::error::Result;
886 /// # use chromiumoxide::layout::Point;
887 /// # async fn demo(page: Page, point: Point) -> Result<()> {
888 /// let html = page.double_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
889 /// # Ok(())
890 /// # }
891 /// ```
892 /// ```
893 pub async fn click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
894 self.inner.click_with_modifier(point, modifiers).await?;
895 Ok(self)
896 }
897
898 /// Performs a click-and-drag mouse event from a starting point to a destination.
899 ///
900 /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
901 /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
902 /// event to the end location, and finally a `MouseReleased` event to complete the drag.
903 ///
904 /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
905 ///
906 /// # Example
907 ///
908 /// Perform a drag from point A to point B using the Shift modifier:
909 ///
910 /// ```no_run
911 /// # use chromiumoxide::page::Page;
912 /// # use chromiumoxide::error::Result;
913 /// # use chromiumoxide::layout::Point;
914 /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
915 /// page.click_and_drag_with_modifier(from, to, 8).await?;
916 /// Ok(())
917 /// # }
918 /// ```
919 pub async fn click_and_drag(&self, from: Point, to: Point) -> Result<&Self> {
920 self.inner.click_and_drag(from, to, 0).await?;
921 Ok(self)
922 }
923
924 /// Performs a click-and-drag mouse event from a starting point to a destination,
925 /// with optional keyboard modifiers: Alt = 1, Ctrl = 2, Meta/Command = 4, Shift = 8 (default: 0).
926 ///
927 /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
928 /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
929 /// event to the end location, and finally a `MouseReleased` event to complete the drag.
930 ///
931 /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
932 ///
933 /// # Example
934 ///
935 /// Perform a drag from point A to point B using the Shift modifier:
936 ///
937 /// ```no_run
938 /// # use chromiumoxide::page::Page;
939 /// # use chromiumoxide::error::Result;
940 /// # use chromiumoxide::layout::Point;
941 /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
942 /// page.click_and_drag_with_modifier(from, to, 8).await?;
943 /// Ok(())
944 /// # }
945 /// ```
946 pub async fn click_and_drag_with_modifier(
947 &self,
948 from: Point,
949 to: Point,
950 modifiers: i64,
951 ) -> Result<&Self> {
952 self.inner.click_and_drag(from, to, modifiers).await?;
953 Ok(self)
954 }
955
956 /// Performs a double mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
957 ///
958 /// This scrolls the point into view first, then executes a
959 /// `DispatchMouseEventParams` command of type `MouseLeft` with
960 /// `MousePressed` as single click and then releases the mouse with an
961 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
962 /// `MouseReleased`
963 ///
964 /// Bear in mind that if `click()` triggers a navigation the new page is not
965 /// immediately loaded when `click()` resolves. To wait until navigation is
966 /// finished an additional `wait_for_navigation()` is required:
967 ///
968 /// # Example
969 ///
970 /// Trigger a navigation and wait until the triggered navigation is finished
971 ///
972 /// ```no_run
973 /// # use chromiumoxide::page::Page;
974 /// # use chromiumoxide::error::Result;
975 /// # use chromiumoxide::layout::Point;
976 /// # async fn demo(page: Page, point: Point) -> Result<()> {
977 /// let html = page.double_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
978 /// # Ok(())
979 /// # }
980 /// ```
981 /// ```
982 pub async fn double_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
983 self.inner
984 .double_click_with_modifier(point, modifiers)
985 .await?;
986 Ok(self)
987 }
988
989 /// Dispatches a `mouseMoved` event and moves the mouse to the position of
990 /// the `point` where `Point.x` is the horizontal position of the mouse and
991 /// `Point.y` the vertical position of the mouse.
992 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
993 self.inner.move_mouse(point).await?;
994 Ok(self)
995 }
996
997 /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
998 /// keys.
999 pub async fn press_key(&self, input: impl AsRef<str>) -> Result<&Self> {
1000 self.inner.press_key(input).await?;
1001 Ok(self)
1002 }
1003
1004 /// Dispatches a `DragEvent`, moving the element to the given `point`.
1005 ///
1006 /// `point.x` defines the horizontal target, and `point.y` the vertical mouse position.
1007 /// Accepts `drag_type`, `drag_data`, and optional keyboard `modifiers`.
1008 pub async fn drag(
1009 &self,
1010 drag_type: DispatchDragEventType,
1011 point: Point,
1012 drag_data: DragData,
1013 modifiers: Option<i64>,
1014 ) -> Result<&Self> {
1015 self.inner
1016 .drag(drag_type, point, drag_data, modifiers)
1017 .await?;
1018 Ok(self)
1019 }
1020
1021 /// Dispatches a `mouseWheel` event and moves the mouse to the position of
1022 /// the `point` where `Point.x` is the horizontal position of the mouse and
1023 /// `Point.y` the vertical position of the mouse.
1024 pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
1025 self.inner.scroll(point, delta).await?;
1026 Ok(self)
1027 }
1028
1029 /// Scrolls the current page by the specified horizontal and vertical offsets.
1030 /// This method helps when Chrome version may not support certain CDP dispatch events.
1031 pub async fn scroll_by(
1032 &self,
1033 delta_x: f64,
1034 delta_y: f64,
1035 behavior: ScrollBehavior,
1036 ) -> Result<&Self> {
1037 self.inner.scroll_by(delta_x, delta_y, behavior).await?;
1038 Ok(self)
1039 }
1040
1041 /// Take a screenshot of the current page
1042 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1043 self.inner.screenshot(params).await
1044 }
1045
1046 /// Take a screenshot of the current page
1047 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1048 self.inner.print_to_pdf(params).await
1049 }
1050
1051 /// Save a screenshot of the page
1052 ///
1053 /// # Example save a png file of a website
1054 ///
1055 /// ```no_run
1056 /// # use chromiumoxide::page::{Page, ScreenshotParams};
1057 /// # use chromiumoxide::error::Result;
1058 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::CaptureScreenshotFormat;
1059 /// # async fn demo(page: Page) -> Result<()> {
1060 /// page.goto("http://example.com")
1061 /// .await?
1062 /// .save_screenshot(
1063 /// ScreenshotParams::builder()
1064 /// .format(CaptureScreenshotFormat::Png)
1065 /// .full_page(true)
1066 /// .omit_background(true)
1067 /// .build(),
1068 /// "example.png",
1069 /// )
1070 /// .await?;
1071 /// # Ok(())
1072 /// # }
1073 /// ```
1074 pub async fn save_screenshot(
1075 &self,
1076 params: impl Into<ScreenshotParams>,
1077 output: impl AsRef<Path>,
1078 ) -> Result<Vec<u8>> {
1079 let img = self.screenshot(params).await?;
1080 utils::write(output.as_ref(), &img).await?;
1081 Ok(img)
1082 }
1083
1084 /// Print the current page as pdf.
1085 ///
1086 /// See [`PrintToPdfParams`]
1087 ///
1088 /// # Note Generating a pdf is currently only supported in Chrome headless.
1089 pub async fn pdf(&self, params: PrintToPdfParams) -> Result<Vec<u8>> {
1090 let res = self.execute(params).await?;
1091 Ok(utils::base64::decode(&res.data)?)
1092 }
1093
1094 /// Save the current page as pdf as file to the `output` path and return the
1095 /// pdf contents.
1096 ///
1097 /// # Note Generating a pdf is currently only supported in Chrome headless.
1098 pub async fn save_pdf(
1099 &self,
1100 opts: PrintToPdfParams,
1101 output: impl AsRef<Path>,
1102 ) -> Result<Vec<u8>> {
1103 let pdf = self.pdf(opts).await?;
1104 utils::write(output.as_ref(), &pdf).await?;
1105 Ok(pdf)
1106 }
1107
1108 /// Brings page to front (activates tab)
1109 pub async fn bring_to_front(&self) -> Result<&Self> {
1110 self.execute(BringToFrontParams::default()).await?;
1111 Ok(self)
1112 }
1113
1114 /// Emulates the given media type or media feature for CSS media queries
1115 pub async fn emulate_media_features(&self, features: Vec<MediaFeature>) -> Result<&Self> {
1116 self.execute(SetEmulatedMediaParams::builder().features(features).build())
1117 .await?;
1118 Ok(self)
1119 }
1120
1121 /// Changes the CSS media type of the page
1122 // Based on https://pptr.dev/api/puppeteer.page.emulatemediatype
1123 pub async fn emulate_media_type(
1124 &self,
1125 media_type: impl Into<MediaTypeParams>,
1126 ) -> Result<&Self> {
1127 self.execute(
1128 SetEmulatedMediaParams::builder()
1129 .media(media_type.into())
1130 .build(),
1131 )
1132 .await?;
1133 Ok(self)
1134 }
1135
1136 /// Overrides default host system timezone
1137 pub async fn emulate_timezone(
1138 &self,
1139 timezoune_id: impl Into<SetTimezoneOverrideParams>,
1140 ) -> Result<&Self> {
1141 self.execute(timezoune_id.into()).await?;
1142 Ok(self)
1143 }
1144
1145 /// Overrides default host system locale with the specified one
1146 pub async fn emulate_locale(
1147 &self,
1148 locale: impl Into<SetLocaleOverrideParams>,
1149 ) -> Result<&Self> {
1150 self.execute(locale.into()).await?;
1151 Ok(self)
1152 }
1153
1154 /// Overrides default viewport
1155 pub async fn emulate_viewport(
1156 &self,
1157 viewport: impl Into<SetDeviceMetricsOverrideParams>,
1158 ) -> Result<&Self> {
1159 self.execute(viewport.into()).await?;
1160 Ok(self)
1161 }
1162
1163 /// Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.
1164 pub async fn emulate_geolocation(
1165 &self,
1166 geolocation: impl Into<SetGeolocationOverrideParams>,
1167 ) -> Result<&Self> {
1168 self.execute(geolocation.into()).await?;
1169 Ok(self)
1170 }
1171
1172 /// Reloads given page
1173 ///
1174 /// To reload ignoring cache run:
1175 /// ```no_run
1176 /// # use chromiumoxide::page::Page;
1177 /// # use chromiumoxide::error::Result;
1178 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
1179 /// # async fn demo(page: Page) -> Result<()> {
1180 /// page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
1181 /// page.wait_for_navigation().await?;
1182 /// # Ok(())
1183 /// # }
1184 /// ```
1185 pub async fn reload(&self) -> Result<&Self> {
1186 self.execute(ReloadParams::default()).await?;
1187 self.wait_for_navigation().await
1188 }
1189
1190 /// Enables ServiceWorkers. Disabled by default.
1191 /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1192 pub async fn enable_service_workers(&self) -> Result<&Self> {
1193 self.execute(browser_protocol::service_worker::EnableParams::default())
1194 .await?;
1195 Ok(self)
1196 }
1197
1198 /// Disables ServiceWorker. Disabled by default.
1199 /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1200 pub async fn disable_service_workers(&self) -> Result<&Self> {
1201 self.execute(browser_protocol::service_worker::DisableParams::default())
1202 .await?;
1203 Ok(self)
1204 }
1205
1206 /// Enables Overlay domain notifications. Disabled by default.
1207 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1208 pub async fn enable_overlay(&self) -> Result<&Self> {
1209 self.execute(browser_protocol::overlay::EnableParams::default())
1210 .await?;
1211 Ok(self)
1212 }
1213
1214 /// Disables Overlay domain notifications. Disabled by default.
1215 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1216 pub async fn disable_overlay(&self) -> Result<&Self> {
1217 self.execute(browser_protocol::overlay::DisableParams::default())
1218 .await?;
1219 Ok(self)
1220 }
1221
1222 /// Enables Overlay domain paint rectangles. Disabled by default.
1223 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1224 pub async fn enable_paint_rectangles(&self) -> Result<&Self> {
1225 self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1226 true,
1227 ))
1228 .await?;
1229 Ok(self)
1230 }
1231
1232 /// Disabled Overlay domain paint rectangles. Disabled by default.
1233 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1234 pub async fn disable_paint_rectangles(&self) -> Result<&Self> {
1235 self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1236 false,
1237 ))
1238 .await?;
1239 Ok(self)
1240 }
1241
1242 /// Enables log domain. Enabled by default.
1243 ///
1244 /// Sends the entries collected so far to the client by means of the
1245 /// entryAdded notification.
1246 ///
1247 /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-enable
1248 pub async fn enable_log(&self) -> Result<&Self> {
1249 self.execute(browser_protocol::log::EnableParams::default())
1250 .await?;
1251 Ok(self)
1252 }
1253
1254 /// Disables log domain
1255 ///
1256 /// Prevents further log entries from being reported to the client
1257 ///
1258 /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-disable
1259 pub async fn disable_log(&self) -> Result<&Self> {
1260 self.execute(browser_protocol::log::DisableParams::default())
1261 .await?;
1262 Ok(self)
1263 }
1264
1265 /// Enables runtime domain. Activated by default.
1266 pub async fn enable_runtime(&self) -> Result<&Self> {
1267 self.execute(js_protocol::runtime::EnableParams::default())
1268 .await?;
1269 Ok(self)
1270 }
1271
1272 /// Disables runtime domain.
1273 pub async fn disable_runtime(&self) -> Result<&Self> {
1274 self.execute(js_protocol::runtime::DisableParams::default())
1275 .await?;
1276 Ok(self)
1277 }
1278
1279 /// Enables Debugger. Enabled by default.
1280 pub async fn enable_debugger(&self) -> Result<&Self> {
1281 self.execute(js_protocol::debugger::EnableParams::default())
1282 .await?;
1283 Ok(self)
1284 }
1285
1286 /// Disables Debugger.
1287 pub async fn disable_debugger(&self) -> Result<&Self> {
1288 self.execute(js_protocol::debugger::DisableParams::default())
1289 .await?;
1290 Ok(self)
1291 }
1292
1293 // Enables DOM agent
1294 pub async fn enable_dom(&self) -> Result<&Self> {
1295 self.execute(browser_protocol::dom::EnableParams::default())
1296 .await?;
1297 Ok(self)
1298 }
1299
1300 // Disables DOM agent
1301 pub async fn disable_dom(&self) -> Result<&Self> {
1302 self.execute(browser_protocol::dom::DisableParams::default())
1303 .await?;
1304 Ok(self)
1305 }
1306
1307 // Enables the CSS agent
1308 pub async fn enable_css(&self) -> Result<&Self> {
1309 self.execute(browser_protocol::css::EnableParams::default())
1310 .await?;
1311 Ok(self)
1312 }
1313
1314 // Disables the CSS agent
1315 pub async fn disable_css(&self) -> Result<&Self> {
1316 self.execute(browser_protocol::css::DisableParams::default())
1317 .await?;
1318 Ok(self)
1319 }
1320
1321 /// Block urls from networking.
1322 ///
1323 /// Prevents further networking
1324 ///
1325 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1326 pub async fn set_blocked_urls(&self, urls: Vec<String>) -> Result<&Self> {
1327 self.execute(SetBlockedUrLsParams::new(urls)).await?;
1328 Ok(self)
1329 }
1330
1331 /// Block all urls from networking.
1332 ///
1333 /// Prevents further networking
1334 ///
1335 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1336 pub async fn block_all_urls(&self) -> Result<&Self> {
1337 self.execute(SetBlockedUrLsParams::new(vec!["*".into()]))
1338 .await?;
1339 Ok(self)
1340 }
1341
1342 /// Activates (focuses) the target.
1343 pub async fn activate(&self) -> Result<&Self> {
1344 self.inner.activate().await?;
1345 Ok(self)
1346 }
1347
1348 /// Returns all cookies that match the tab's current URL.
1349 pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
1350 Ok(self
1351 .execute(GetCookiesParams::default())
1352 .await?
1353 .result
1354 .cookies)
1355 }
1356
1357 /// Set a single cookie
1358 ///
1359 /// This fails if the cookie's url or if not provided, the page's url is
1360 /// `about:blank` or a `data:` url.
1361 ///
1362 /// # Example
1363 /// ```no_run
1364 /// # use chromiumoxide::page::Page;
1365 /// # use chromiumoxide::error::Result;
1366 /// # use chromiumoxide_cdp::cdp::browser_protocol::network::CookieParam;
1367 /// # async fn demo(page: Page) -> Result<()> {
1368 /// page.set_cookie(CookieParam::new("Cookie-name", "Cookie-value")).await?;
1369 /// # Ok(())
1370 /// # }
1371 /// ```
1372 pub async fn set_cookie(&self, cookie: impl Into<CookieParam>) -> Result<&Self> {
1373 let mut cookie = cookie.into();
1374 if let Some(url) = cookie.url.as_ref() {
1375 validate_cookie_url(url)?;
1376 } else {
1377 let url = self
1378 .url()
1379 .await?
1380 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1381 validate_cookie_url(&url)?;
1382 if url.starts_with("http") {
1383 cookie.url = Some(url);
1384 }
1385 }
1386 self.execute(DeleteCookiesParams::from_cookie(&cookie))
1387 .await?;
1388 self.execute(SetCookiesParams::new(vec![cookie])).await?;
1389 Ok(self)
1390 }
1391
1392 /// Set all the cookies
1393 pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
1394 let url = self
1395 .url()
1396 .await?
1397 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1398 let is_http = url.starts_with("http");
1399 if !is_http {
1400 validate_cookie_url(&url)?;
1401 }
1402
1403 for cookie in &mut cookies {
1404 if let Some(url) = cookie.url.as_ref() {
1405 validate_cookie_url(url)?;
1406 } else if is_http {
1407 cookie.url = Some(url.clone());
1408 }
1409 }
1410 self.delete_cookies_unchecked(cookies.iter().map(DeleteCookiesParams::from_cookie))
1411 .await?;
1412
1413 self.execute(SetCookiesParams::new(cookies)).await?;
1414 Ok(self)
1415 }
1416
1417 /// Delete a single cookie
1418 pub async fn delete_cookie(&self, cookie: impl Into<DeleteCookiesParams>) -> Result<&Self> {
1419 let mut cookie = cookie.into();
1420 if cookie.url.is_none() {
1421 let url = self
1422 .url()
1423 .await?
1424 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1425 if url.starts_with("http") {
1426 cookie.url = Some(url);
1427 }
1428 }
1429 self.execute(cookie).await?;
1430 Ok(self)
1431 }
1432
1433 /// Delete all the cookies
1434 pub async fn delete_cookies(&self, mut cookies: Vec<DeleteCookiesParams>) -> Result<&Self> {
1435 let mut url: Option<(String, bool)> = None;
1436 for cookie in &mut cookies {
1437 if cookie.url.is_none() {
1438 if let Some((url, is_http)) = url.as_ref() {
1439 if *is_http {
1440 cookie.url = Some(url.clone())
1441 }
1442 } else {
1443 let page_url = self
1444 .url()
1445 .await?
1446 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1447 let is_http = page_url.starts_with("http");
1448 if is_http {
1449 cookie.url = Some(page_url.clone())
1450 }
1451 url = Some((page_url, is_http));
1452 }
1453 }
1454 }
1455 self.delete_cookies_unchecked(cookies.into_iter()).await?;
1456 Ok(self)
1457 }
1458
1459 /// Convenience method that prevents another channel roundtrip to get the
1460 /// url and validate it
1461 async fn delete_cookies_unchecked(
1462 &self,
1463 cookies: impl Iterator<Item = DeleteCookiesParams>,
1464 ) -> Result<&Self> {
1465 // NOTE: the buffer size is arbitrary
1466 let mut cmds = stream::iter(cookies.into_iter().map(|cookie| self.execute(cookie)))
1467 .buffer_unordered(5);
1468 while let Some(resp) = cmds.next().await {
1469 resp?;
1470 }
1471 Ok(self)
1472 }
1473
1474 /// Returns the title of the document.
1475 pub async fn get_title(&self) -> Result<Option<String>> {
1476 let result = self.evaluate("document.title").await?;
1477
1478 let title: String = result.into_value()?;
1479
1480 if title.is_empty() {
1481 Ok(None)
1482 } else {
1483 Ok(Some(title))
1484 }
1485 }
1486
1487 /// Retrieve current values of run-time metrics.
1488 pub async fn metrics(&self) -> Result<Vec<Metric>> {
1489 Ok(self
1490 .execute(GetMetricsParams::default())
1491 .await?
1492 .result
1493 .metrics)
1494 }
1495
1496 /// Returns metrics relating to the layout of the page
1497 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1498 self.inner.layout_metrics().await
1499 }
1500
1501 /// This evaluates strictly as expression.
1502 ///
1503 /// Same as `Page::evaluate` but no fallback or any attempts to detect
1504 /// whether the expression is actually a function. However you can
1505 /// submit a function evaluation string:
1506 ///
1507 /// # Example Evaluate function call as expression
1508 ///
1509 /// This will take the arguments `(1,2)` and will call the function
1510 ///
1511 /// ```no_run
1512 /// # use chromiumoxide::page::Page;
1513 /// # use chromiumoxide::error::Result;
1514 /// # async fn demo(page: Page) -> Result<()> {
1515 /// let sum: usize = page
1516 /// .evaluate_expression("((a,b) => {return a + b;})(1,2)")
1517 /// .await?
1518 /// .into_value()?;
1519 /// assert_eq!(sum, 3);
1520 /// # Ok(())
1521 /// # }
1522 /// ```
1523 pub async fn evaluate_expression(
1524 &self,
1525 evaluate: impl Into<EvaluateParams>,
1526 ) -> Result<EvaluationResult> {
1527 self.inner.evaluate_expression(evaluate).await
1528 }
1529
1530 /// Evaluates an expression or function in the page's context and returns
1531 /// the result.
1532 ///
1533 /// In contrast to `Page::evaluate_expression` this is capable of handling
1534 /// function calls and expressions alike. This takes anything that is
1535 /// `Into<Evaluation>`. When passing a `String` or `str`, this will try to
1536 /// detect whether it is a function or an expression. JS function detection
1537 /// is not very sophisticated but works for general cases (`(async)
1538 /// functions` and arrow functions). If you want a string statement
1539 /// specifically evaluated as expression or function either use the
1540 /// designated functions `Page::evaluate_function` or
1541 /// `Page::evaluate_expression` or use the proper parameter type for
1542 /// `Page::execute`: `EvaluateParams` for strict expression evaluation or
1543 /// `CallFunctionOnParams` for strict function evaluation.
1544 ///
1545 /// If you don't trust the js function detection and are not sure whether
1546 /// the statement is an expression or of type function (arrow functions: `()
1547 /// => {..}`), you should pass it as `EvaluateParams` and set the
1548 /// `EvaluateParams::eval_as_function_fallback` option. This will first
1549 /// try to evaluate it as expression and if the result comes back
1550 /// evaluated as `RemoteObjectType::Function` it will submit the
1551 /// statement again but as function:
1552 ///
1553 /// # Example Evaluate function statement as expression with fallback
1554 /// option
1555 ///
1556 /// ```no_run
1557 /// # use chromiumoxide::page::Page;
1558 /// # use chromiumoxide::error::Result;
1559 /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{EvaluateParams, RemoteObjectType};
1560 /// # async fn demo(page: Page) -> Result<()> {
1561 /// let eval = EvaluateParams::builder().expression("() => {return 42;}");
1562 /// // this will fail because the `EvaluationResult` returned by the browser will be
1563 /// // of type `Function`
1564 /// let result = page
1565 /// .evaluate(eval.clone().build().unwrap())
1566 /// .await?;
1567 /// assert_eq!(result.object().r#type, RemoteObjectType::Function);
1568 /// assert!(result.into_value::<usize>().is_err());
1569 ///
1570 /// // This will also fail on the first try but it detects that the browser evaluated the
1571 /// // statement as function and then evaluate it again but as function
1572 /// let sum: usize = page
1573 /// .evaluate(eval.eval_as_function_fallback(true).build().unwrap())
1574 /// .await?
1575 /// .into_value()?;
1576 /// # Ok(())
1577 /// # }
1578 /// ```
1579 ///
1580 /// # Example Evaluate basic expression
1581 /// ```no_run
1582 /// # use chromiumoxide::page::Page;
1583 /// # use chromiumoxide::error::Result;
1584 /// # async fn demo(page: Page) -> Result<()> {
1585 /// let sum:usize = page.evaluate("1 + 2").await?.into_value()?;
1586 /// assert_eq!(sum, 3);
1587 /// # Ok(())
1588 /// # }
1589 /// ```
1590 pub async fn evaluate(&self, evaluate: impl Into<Evaluation>) -> Result<EvaluationResult> {
1591 match evaluate.into() {
1592 Evaluation::Expression(mut expr) => {
1593 if expr.context_id.is_none() {
1594 expr.context_id = self.execution_context().await?;
1595 }
1596 let fallback = expr.eval_as_function_fallback.and_then(|p| {
1597 if p {
1598 Some(expr.clone())
1599 } else {
1600 None
1601 }
1602 });
1603 let res = self.evaluate_expression(expr).await?;
1604
1605 if res.object().r#type == RemoteObjectType::Function {
1606 // expression was actually a function
1607 if let Some(fallback) = fallback {
1608 return self.evaluate_function(fallback).await;
1609 }
1610 }
1611 Ok(res)
1612 }
1613 Evaluation::Function(fun) => Ok(self.evaluate_function(fun).await?),
1614 }
1615 }
1616
1617 /// Eexecutes a function withinthe page's context and returns the result.
1618 ///
1619 /// # Example Evaluate a promise
1620 /// This will wait until the promise resolves and then returns the result.
1621 /// ```no_run
1622 /// # use chromiumoxide::page::Page;
1623 /// # use chromiumoxide::error::Result;
1624 /// # async fn demo(page: Page) -> Result<()> {
1625 /// let sum:usize = page.evaluate_function("() => Promise.resolve(1 + 2)").await?.into_value()?;
1626 /// assert_eq!(sum, 3);
1627 /// # Ok(())
1628 /// # }
1629 /// ```
1630 ///
1631 /// # Example Evaluate an async function
1632 /// ```no_run
1633 /// # use chromiumoxide::page::Page;
1634 /// # use chromiumoxide::error::Result;
1635 /// # async fn demo(page: Page) -> Result<()> {
1636 /// let val:usize = page.evaluate_function("async function() {return 42;}").await?.into_value()?;
1637 /// assert_eq!(val, 42);
1638 /// # Ok(())
1639 /// # }
1640 /// ```
1641 /// # Example Construct a function call
1642 ///
1643 /// ```no_run
1644 /// # use chromiumoxide::page::Page;
1645 /// # use chromiumoxide::error::Result;
1646 /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{CallFunctionOnParams, CallArgument};
1647 /// # async fn demo(page: Page) -> Result<()> {
1648 /// let call = CallFunctionOnParams::builder()
1649 /// .function_declaration(
1650 /// "(a,b) => { return a + b;}"
1651 /// )
1652 /// .argument(
1653 /// CallArgument::builder()
1654 /// .value(serde_json::json!(1))
1655 /// .build(),
1656 /// )
1657 /// .argument(
1658 /// CallArgument::builder()
1659 /// .value(serde_json::json!(2))
1660 /// .build(),
1661 /// )
1662 /// .build()
1663 /// .unwrap();
1664 /// let sum:usize = page.evaluate_function(call).await?.into_value()?;
1665 /// assert_eq!(sum, 3);
1666 /// # Ok(())
1667 /// # }
1668 /// ```
1669 pub async fn evaluate_function(
1670 &self,
1671 evaluate: impl Into<CallFunctionOnParams>,
1672 ) -> Result<EvaluationResult> {
1673 self.inner.evaluate_function(evaluate).await
1674 }
1675
1676 /// Returns the default execution context identifier of this page that
1677 /// represents the context for JavaScript execution.
1678 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
1679 self.inner.execution_context().await
1680 }
1681
1682 /// Returns the secondary execution context identifier of this page that
1683 /// represents the context for JavaScript execution for manipulating the
1684 /// DOM.
1685 ///
1686 /// See `Page::set_contents`
1687 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
1688 self.inner.secondary_execution_context().await
1689 }
1690
1691 pub async fn frame_execution_context(
1692 &self,
1693 frame_id: FrameId,
1694 ) -> Result<Option<ExecutionContextId>> {
1695 self.inner.frame_execution_context(frame_id).await
1696 }
1697
1698 pub async fn frame_secondary_execution_context(
1699 &self,
1700 frame_id: FrameId,
1701 ) -> Result<Option<ExecutionContextId>> {
1702 self.inner.frame_secondary_execution_context(frame_id).await
1703 }
1704
1705 /// Evaluates given script in every frame upon creation (before loading
1706 /// frame's scripts)
1707 pub async fn evaluate_on_new_document(
1708 &self,
1709 script: impl Into<AddScriptToEvaluateOnNewDocumentParams>,
1710 ) -> Result<ScriptIdentifier> {
1711 Ok(self.execute(script.into()).await?.result.identifier)
1712 }
1713
1714 /// Set the content of the frame.
1715 ///
1716 /// # Example
1717 /// ```no_run
1718 /// # use chromiumoxide::page::Page;
1719 /// # use chromiumoxide::error::Result;
1720 /// # async fn demo(page: Page) -> Result<()> {
1721 /// page.set_content("<body>
1722 /// <h1>This was set via chromiumoxide</h1>
1723 /// </body>").await?;
1724 /// # Ok(())
1725 /// # }
1726 /// ```
1727 pub async fn set_content(&self, html: impl AsRef<str>) -> Result<&Self> {
1728 let mut call = CallFunctionOnParams::builder()
1729 .function_declaration(
1730 "(html) => {
1731 document.open();
1732 document.write(html);
1733 document.close();
1734 }",
1735 )
1736 .argument(
1737 CallArgument::builder()
1738 .value(serde_json::json!(html.as_ref()))
1739 .build(),
1740 )
1741 .build()
1742 .unwrap();
1743
1744 call.execution_context_id = self
1745 .inner
1746 .execution_context_for_world(None, DOMWorldKind::Secondary)
1747 .await?;
1748
1749 self.evaluate_function(call).await?;
1750 // relying that document.open() will reset frame lifecycle with "init"
1751 // lifecycle event. @see https://crrev.com/608658
1752 self.wait_for_navigation().await
1753 }
1754
1755 /// Returns the HTML content of the page.
1756 pub async fn content(&self) -> Result<String> {
1757 Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
1758 }
1759
1760 #[cfg(feature = "bytes")]
1761 /// Returns the HTML content of the page
1762 pub async fn content_bytes(&self) -> Result<Vec<u8>> {
1763 Ok(self.evaluate(OUTER_HTML).await?.into_bytes()?)
1764 }
1765
1766 #[cfg(feature = "bytes")]
1767 /// Returns the full serialized content of the page (HTML or XML)
1768 pub async fn content_bytes_xml(&self) -> Result<Vec<u8>> {
1769 Ok(self.evaluate(FULL_XML_SERIALIZER_JS).await?.into_bytes()?)
1770 }
1771
1772 #[cfg(feature = "bytes")]
1773 /// Returns the HTML outer html of the page
1774 pub async fn outer_html_bytes(&self) -> Result<Vec<u8>> {
1775 Ok(self.outer_html().await?.into())
1776 }
1777
1778 /// Enable Chrome's experimental ad filter on all sites.
1779 pub async fn set_ad_blocking_enabled(&self, enabled: bool) -> Result<&Self> {
1780 self.execute(SetAdBlockingEnabledParams::new(enabled))
1781 .await?;
1782 Ok(self)
1783 }
1784
1785 /// Start to screencast a frame.
1786 pub async fn start_screencast(
1787 &self,
1788 params: impl Into<StartScreencastParams>,
1789 ) -> Result<&Self> {
1790 self.execute(params.into()).await?;
1791 Ok(self)
1792 }
1793
1794 /// Acknowledges that a screencast frame has been received by the frontend.
1795 pub async fn ack_screencast(
1796 &self,
1797 params: impl Into<ScreencastFrameAckParams>,
1798 ) -> Result<&Self> {
1799 self.execute(params.into()).await?;
1800 Ok(self)
1801 }
1802
1803 /// Stop screencast a frame.
1804 pub async fn stop_screencast(&self, params: impl Into<StopScreencastParams>) -> Result<&Self> {
1805 self.execute(params.into()).await?;
1806 Ok(self)
1807 }
1808
1809 /// Returns source for the script with given id.
1810 ///
1811 /// Debugger must be enabled.
1812 pub async fn get_script_source(&self, script_id: impl Into<String>) -> Result<String> {
1813 Ok(self
1814 .execute(GetScriptSourceParams::new(ScriptId::from(script_id.into())))
1815 .await?
1816 .result
1817 .script_source)
1818 }
1819}
1820
1821impl From<Arc<PageInner>> for Page {
1822 fn from(inner: Arc<PageInner>) -> Self {
1823 Self { inner }
1824 }
1825}
1826
1827pub(crate) fn validate_cookie_url(url: &str) -> Result<()> {
1828 if url.starts_with("data:") {
1829 Err(CdpError::msg("Data URL page can not have cookie"))
1830 } else if url == "about:blank" {
1831 Err(CdpError::msg("Blank page can not have cookie"))
1832 } else {
1833 Ok(())
1834 }
1835}
1836
1837/// Page screenshot parameters with extra options.
1838#[derive(Debug, Default)]
1839pub struct ScreenshotParams {
1840 /// Chrome DevTools Protocol screenshot options.
1841 pub cdp_params: CaptureScreenshotParams,
1842 /// Take full page screenshot.
1843 pub full_page: Option<bool>,
1844 /// Make the background transparent (png only).
1845 pub omit_background: Option<bool>,
1846}
1847
1848impl ScreenshotParams {
1849 pub fn builder() -> ScreenshotParamsBuilder {
1850 Default::default()
1851 }
1852
1853 pub(crate) fn full_page(&self) -> bool {
1854 self.full_page.unwrap_or(false)
1855 }
1856
1857 pub(crate) fn omit_background(&self) -> bool {
1858 self.omit_background.unwrap_or(false)
1859 && self
1860 .cdp_params
1861 .format
1862 .as_ref()
1863 .map_or(true, |f| f == &CaptureScreenshotFormat::Png)
1864 }
1865}
1866
1867/// Page screenshot parameters builder with extra options.
1868#[derive(Debug, Default)]
1869pub struct ScreenshotParamsBuilder {
1870 cdp_params: CaptureScreenshotParams,
1871 full_page: Option<bool>,
1872 omit_background: Option<bool>,
1873}
1874
1875impl ScreenshotParamsBuilder {
1876 /// Image compression format (defaults to png).
1877 pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
1878 self.cdp_params.format = Some(format.into());
1879 self
1880 }
1881
1882 /// Compression quality from range [0..100] (jpeg only).
1883 pub fn quality(mut self, quality: impl Into<i64>) -> Self {
1884 self.cdp_params.quality = Some(quality.into());
1885 self
1886 }
1887
1888 /// Capture the screenshot of a given region only.
1889 pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
1890 self.cdp_params.clip = Some(clip.into());
1891 self
1892 }
1893
1894 /// Capture the screenshot from the surface, rather than the view (defaults to true).
1895 pub fn from_surface(mut self, from_surface: impl Into<bool>) -> Self {
1896 self.cdp_params.from_surface = Some(from_surface.into());
1897 self
1898 }
1899
1900 /// Capture the screenshot beyond the viewport (defaults to false).
1901 pub fn capture_beyond_viewport(mut self, capture_beyond_viewport: impl Into<bool>) -> Self {
1902 self.cdp_params.capture_beyond_viewport = Some(capture_beyond_viewport.into());
1903 self
1904 }
1905
1906 /// Full page screen capture.
1907 pub fn full_page(mut self, full_page: impl Into<bool>) -> Self {
1908 self.full_page = Some(full_page.into());
1909 self
1910 }
1911
1912 /// Make the background transparent (png only)
1913 pub fn omit_background(mut self, omit_background: impl Into<bool>) -> Self {
1914 self.omit_background = Some(omit_background.into());
1915 self
1916 }
1917
1918 pub fn build(self) -> ScreenshotParams {
1919 ScreenshotParams {
1920 cdp_params: self.cdp_params,
1921 full_page: self.full_page,
1922 omit_background: self.omit_background,
1923 }
1924 }
1925}
1926
1927impl From<CaptureScreenshotParams> for ScreenshotParams {
1928 fn from(cdp_params: CaptureScreenshotParams) -> Self {
1929 Self {
1930 cdp_params,
1931 ..Default::default()
1932 }
1933 }
1934}
1935
1936#[derive(Debug, Clone, Copy, Default)]
1937pub enum MediaTypeParams {
1938 /// Default CSS media type behavior for page and print
1939 #[default]
1940 Null,
1941 /// Force screen CSS media type for page and print
1942 Screen,
1943 /// Force print CSS media type for page and print
1944 Print,
1945}
1946impl From<MediaTypeParams> for String {
1947 fn from(media_type: MediaTypeParams) -> Self {
1948 match media_type {
1949 MediaTypeParams::Null => "null".to_string(),
1950 MediaTypeParams::Screen => "screen".to_string(),
1951 MediaTypeParams::Print => "print".to_string(),
1952 }
1953 }
1954}