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