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 single mouse click event at the point's location with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
982 ///
983 /// This scrolls the point into view first, then executes a
984 /// `DispatchMouseEventParams` command of type `MouseLeft` with
985 /// `MousePressed` as single click and then releases the mouse with an
986 /// additional `DispatchMouseEventParams` of type `MouseLeft` 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.click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1003 /// # Ok(())
1004 /// # }
1005 /// ```
1006 /// ```
1007 pub async fn click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1008 self.inner.click_with_modifier(point, modifiers).await?;
1009 Ok(self)
1010 }
1011
1012 /// 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).
1013 ///
1014 /// This scrolls the point into view first, then executes a
1015 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1016 /// `MousePressed` as single click and then releases the mouse with an
1017 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1018 /// `MouseReleased`
1019 ///
1020 /// # Example
1021 ///
1022 /// Trigger a navigation and wait until the triggered navigation is finished
1023 ///
1024 /// ```no_run
1025 /// # use chromiumoxide::page::Page;
1026 /// # use chromiumoxide::error::Result;
1027 /// # use chromiumoxide::layout::Point;
1028 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1029 /// let html = page.right_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1030 /// # Ok(())
1031 /// # }
1032 /// ```
1033 /// ```
1034 pub async fn right_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1035 self.inner
1036 .right_click_with_modifier(point, modifiers)
1037 .await?;
1038 Ok(self)
1039 }
1040
1041 /// 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).
1042 ///
1043 /// This scrolls the point into view first, then executes a
1044 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1045 /// `MousePressed` as single click and then releases the mouse with an
1046 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1047 /// `MouseReleased`
1048 ///
1049 /// # Example
1050 ///
1051 /// Trigger a navigation and wait until the triggered navigation is finished
1052 ///
1053 /// ```no_run
1054 /// # use chromiumoxide::page::Page;
1055 /// # use chromiumoxide::error::Result;
1056 /// # use chromiumoxide::layout::Point;
1057 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1058 /// let html = page.middle_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1059 /// # Ok(())
1060 /// # }
1061 /// ```
1062 /// ```
1063 pub async fn middle_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1064 self.inner
1065 .middle_click_with_modifier(point, modifiers)
1066 .await?;
1067 Ok(self)
1068 }
1069
1070 /// Performs keyboard typing.
1071 ///
1072 /// # Example
1073 ///
1074 /// ```no_run
1075 /// # use chromiumoxide::page::Page;
1076 /// # use chromiumoxide::error::Result;
1077 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1078 /// let html = page.type_str("abc").await?.content();
1079 /// # Ok(())
1080 /// # }
1081 /// ```
1082 pub async fn type_str(&self, input: impl AsRef<str>) -> Result<&Self> {
1083 self.inner.type_str(input).await?;
1084 Ok(self)
1085 }
1086
1087 /// Performs keyboard typing with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0).
1088 ///
1089 /// # Example
1090 ///
1091 /// ```no_run
1092 /// # use chromiumoxide::page::Page;
1093 /// # use chromiumoxide::error::Result;
1094 /// # async fn demo(page: Page, point: Point) -> Result<()> {
1095 /// let html = page.type_str_with_modifier("abc", Some(1)).await?.content();
1096 /// # Ok(())
1097 /// # }
1098 /// ```
1099 pub async fn type_str_with_modifier(
1100 &self,
1101 input: impl AsRef<str>,
1102 modifiers: Option<i64>,
1103 ) -> Result<&Self> {
1104 self.inner.type_str_with_modifier(input, modifiers).await?;
1105 Ok(self)
1106 }
1107
1108 /// Performs a click-and-drag mouse event from a starting point to a destination.
1109 ///
1110 /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
1111 /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
1112 /// event to the end location, and finally a `MouseReleased` event to complete the drag.
1113 ///
1114 /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
1115 ///
1116 /// # Example
1117 ///
1118 /// Perform a drag from point A to point B using the Shift modifier:
1119 ///
1120 /// ```no_run
1121 /// # use chromiumoxide::page::Page;
1122 /// # use chromiumoxide::error::Result;
1123 /// # use chromiumoxide::layout::Point;
1124 /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
1125 /// page.click_and_drag_with_modifier(from, to, 8).await?;
1126 /// Ok(())
1127 /// # }
1128 /// ```
1129 pub async fn click_and_drag(&self, from: Point, to: Point) -> Result<&Self> {
1130 self.inner.click_and_drag(from, to, 0).await?;
1131 Ok(self)
1132 }
1133
1134 /// Performs a click-and-drag mouse event from a starting point to a destination,
1135 /// with optional keyboard modifiers: Alt = 1, Ctrl = 2, Meta/Command = 4, Shift = 8 (default: 0).
1136 ///
1137 /// This scrolls both points into view and dispatches a sequence of `DispatchMouseEventParams`
1138 /// commands in order: a `MousePressed` event at the start location, followed by a `MouseMoved`
1139 /// event to the end location, and finally a `MouseReleased` event to complete the drag.
1140 ///
1141 /// This is useful for dragging UI elements, sliders, or simulating mouse gestures.
1142 ///
1143 /// # Example
1144 ///
1145 /// Perform a drag from point A to point B using the Shift modifier:
1146 ///
1147 /// ```no_run
1148 /// # use chromiumoxide::page::Page;
1149 /// # use chromiumoxide::error::Result;
1150 /// # use chromiumoxide::layout::Point;
1151 /// # async fn demo(page: Page, from: Point, to: Point) -> Result<()> {
1152 /// page.click_and_drag_with_modifier(from, to, 8).await?;
1153 /// Ok(())
1154 /// # }
1155 /// ```
1156 pub async fn click_and_drag_with_modifier(
1157 &self,
1158 from: Point,
1159 to: Point,
1160 modifiers: i64,
1161 ) -> Result<&Self> {
1162 self.inner.click_and_drag(from, to, modifiers).await?;
1163 Ok(self)
1164 }
1165
1166 /// 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).
1167 ///
1168 /// This scrolls the point into view first, then executes a
1169 /// `DispatchMouseEventParams` command of type `MouseLeft` with
1170 /// `MousePressed` as single click and then releases the mouse with an
1171 /// additional `DispatchMouseEventParams` of type `MouseLeft` with
1172 /// `MouseReleased`
1173 ///
1174 /// Bear in mind that if `click()` triggers a navigation the new page is not
1175 /// immediately loaded when `click()` resolves. To wait until navigation is
1176 /// finished an additional `wait_for_navigation()` is required:
1177 ///
1178 /// # Example
1179 ///
1180 /// Trigger a navigation and wait until the triggered navigation is finished
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, point: Point) -> Result<()> {
1187 /// let html = page.double_click_with_modifier(point, 1).await?.wait_for_navigation().await?.content();
1188 /// # Ok(())
1189 /// # }
1190 /// ```
1191 /// ```
1192 pub async fn double_click_with_modifier(&self, point: Point, modifiers: i64) -> Result<&Self> {
1193 self.inner
1194 .double_click_with_modifier(point, modifiers)
1195 .await?;
1196 Ok(self)
1197 }
1198
1199 /// Dispatches a `mouseMoved` event and moves the mouse to the position of
1200 /// the `point` where `Point.x` is the horizontal position of the mouse and
1201 /// `Point.y` the vertical position of the mouse.
1202 pub async fn move_mouse(&self, point: Point) -> Result<&Self> {
1203 self.inner.move_mouse(point).await?;
1204 Ok(self)
1205 }
1206
1207 /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
1208 /// keys.
1209 pub async fn press_key(&self, input: impl AsRef<str>) -> Result<&Self> {
1210 self.inner.press_key(input).await?;
1211 Ok(self)
1212 }
1213
1214 /// Uses the `DispatchKeyEvent` mechanism to simulate pressing keyboard
1215 /// keys with the modifier: Alt=1, Ctrl=2, Meta/Command=4, Shift=8\n(default: 0)..
1216 pub async fn press_key_with_modifier(
1217 &self,
1218 input: impl AsRef<str>,
1219 modifiers: i64,
1220 ) -> Result<&Self> {
1221 self.inner
1222 .press_key_with_modifier(input, Some(modifiers))
1223 .await?;
1224 Ok(self)
1225 }
1226
1227 /// Dispatches a `DragEvent`, moving the element to the given `point`.
1228 ///
1229 /// `point.x` defines the horizontal target, and `point.y` the vertical mouse position.
1230 /// Accepts `drag_type`, `drag_data`, and optional keyboard `modifiers`.
1231 pub async fn drag(
1232 &self,
1233 drag_type: DispatchDragEventType,
1234 point: Point,
1235 drag_data: DragData,
1236 modifiers: Option<i64>,
1237 ) -> Result<&Self> {
1238 self.inner
1239 .drag(drag_type, point, drag_data, modifiers)
1240 .await?;
1241 Ok(self)
1242 }
1243 /// Fetches the entire accessibility tree for the root Document
1244 ///
1245 /// # Example
1246 ///
1247 /// ```no_run
1248 /// # use chromiumoxide::page::Page;
1249 /// # use chromiumoxide::error::Result;
1250 /// # async fn demo_get_full_ax_tree(page: Page, depth: Option<i64>, frame_id: Option<FrameId>) -> Result<()> {
1251 /// let tree = page.get_full_ax_tree(None, None).await;
1252 /// # Ok(())
1253 /// # }
1254 /// ```
1255 pub async fn get_full_ax_tree(
1256 &self,
1257 depth: Option<i64>,
1258 frame_id: Option<FrameId>,
1259 ) -> Result<GetFullAxTreeReturns> {
1260 self.inner.get_full_ax_tree(depth, frame_id).await
1261 }
1262
1263 /// Fetches the partial accessibility tree for the root Document
1264 ///
1265 /// # Example
1266 ///
1267 /// ```no_run
1268 /// # use chromiumoxide::page::Page;
1269 /// # use chromiumoxide::error::Result;
1270 /// # 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<()> {
1271 /// let tree = page.get_partial_ax_tree(node_id, backend_node_id, object_id, fetch_relatives).await;
1272 /// # Ok(())
1273 /// # }
1274 /// ```
1275 pub async fn get_partial_ax_tree(
1276 &self,
1277 node_id: Option<chromiumoxide_cdp::cdp::browser_protocol::dom::NodeId>,
1278 backend_node_id: Option<BackendNodeId>,
1279 object_id: Option<chromiumoxide_cdp::cdp::js_protocol::runtime::RemoteObjectId>,
1280 fetch_relatives: Option<bool>,
1281 ) -> Result<GetPartialAxTreeReturns> {
1282 self.inner
1283 .get_partial_ax_tree(node_id, backend_node_id, object_id, fetch_relatives)
1284 .await
1285 }
1286
1287 /// Dispatches a `mouseWheel` event and moves the mouse to the position of
1288 /// the `point` where `Point.x` is the horizontal position of the mouse and
1289 /// `Point.y` the vertical position of the mouse.
1290 pub async fn scroll(&self, point: Point, delta: Delta) -> Result<&Self> {
1291 self.inner.scroll(point, delta).await?;
1292 Ok(self)
1293 }
1294
1295 /// Scrolls the current page by the specified horizontal and vertical offsets.
1296 /// This method helps when Chrome version may not support certain CDP dispatch events.
1297 pub async fn scroll_by(
1298 &self,
1299 delta_x: f64,
1300 delta_y: f64,
1301 behavior: ScrollBehavior,
1302 ) -> Result<&Self> {
1303 self.inner.scroll_by(delta_x, delta_y, behavior).await?;
1304 Ok(self)
1305 }
1306
1307 /// Take a screenshot of the current page
1308 pub async fn screenshot(&self, params: impl Into<ScreenshotParams>) -> Result<Vec<u8>> {
1309 self.inner.screenshot(params).await
1310 }
1311
1312 /// Take a screenshot of the current page
1313 pub async fn print_to_pdf(&self, params: impl Into<PrintToPdfParams>) -> Result<Vec<u8>> {
1314 self.inner.print_to_pdf(params).await
1315 }
1316
1317 /// Save a screenshot of the page
1318 ///
1319 /// # Example save a png file of a website
1320 ///
1321 /// ```no_run
1322 /// # use chromiumoxide::page::{Page, ScreenshotParams};
1323 /// # use chromiumoxide::error::Result;
1324 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::CaptureScreenshotFormat;
1325 /// # async fn demo(page: Page) -> Result<()> {
1326 /// page.goto("http://example.com")
1327 /// .await?
1328 /// .save_screenshot(
1329 /// ScreenshotParams::builder()
1330 /// .format(CaptureScreenshotFormat::Png)
1331 /// .full_page(true)
1332 /// .omit_background(true)
1333 /// .build(),
1334 /// "example.png",
1335 /// )
1336 /// .await?;
1337 /// # Ok(())
1338 /// # }
1339 /// ```
1340 pub async fn save_screenshot(
1341 &self,
1342 params: impl Into<ScreenshotParams>,
1343 output: impl AsRef<Path>,
1344 ) -> Result<Vec<u8>> {
1345 let img = self.screenshot(params).await?;
1346 utils::write(output.as_ref(), &img).await?;
1347 Ok(img)
1348 }
1349
1350 /// Print the current page as pdf.
1351 ///
1352 /// See [`PrintToPdfParams`]
1353 ///
1354 /// # Note Generating a pdf is currently only supported in Chrome headless.
1355 pub async fn pdf(&self, params: PrintToPdfParams) -> Result<Vec<u8>> {
1356 let res = self.execute(params).await?;
1357 Ok(utils::base64::decode(&res.data)?)
1358 }
1359
1360 /// Save the current page as pdf as file to the `output` path and return the
1361 /// pdf contents.
1362 ///
1363 /// # Note Generating a pdf is currently only supported in Chrome headless.
1364 pub async fn save_pdf(
1365 &self,
1366 opts: PrintToPdfParams,
1367 output: impl AsRef<Path>,
1368 ) -> Result<Vec<u8>> {
1369 let pdf = self.pdf(opts).await?;
1370 utils::write(output.as_ref(), &pdf).await?;
1371 Ok(pdf)
1372 }
1373
1374 /// Brings page to front (activates tab)
1375 pub async fn bring_to_front(&self) -> Result<&Self> {
1376 self.execute(BringToFrontParams::default()).await?;
1377 Ok(self)
1378 }
1379
1380 /// Emulates hardware concurrency.
1381 pub async fn emulate_hardware_concurrency(&self, hardware_concurrency: i64) -> Result<&Self> {
1382 self.execute(SetHardwareConcurrencyOverrideParams::new(
1383 hardware_concurrency,
1384 ))
1385 .await?;
1386 Ok(self)
1387 }
1388
1389 /// Emulates the given media type or media feature for CSS media queries
1390 pub async fn emulate_media_features(&self, features: Vec<MediaFeature>) -> Result<&Self> {
1391 self.execute(SetEmulatedMediaParams::builder().features(features).build())
1392 .await?;
1393 Ok(self)
1394 }
1395
1396 /// Changes the CSS media type of the page
1397 // Based on https://pptr.dev/api/puppeteer.page.emulatemediatype
1398 pub async fn emulate_media_type(
1399 &self,
1400 media_type: impl Into<MediaTypeParams>,
1401 ) -> Result<&Self> {
1402 self.execute(
1403 SetEmulatedMediaParams::builder()
1404 .media(media_type.into())
1405 .build(),
1406 )
1407 .await?;
1408 Ok(self)
1409 }
1410
1411 /// Overrides default host system timezone
1412 pub async fn emulate_timezone(
1413 &self,
1414 timezoune_id: impl Into<SetTimezoneOverrideParams>,
1415 ) -> Result<&Self> {
1416 self.execute(timezoune_id.into()).await?;
1417 Ok(self)
1418 }
1419
1420 /// Overrides default host system locale with the specified one
1421 pub async fn emulate_locale(
1422 &self,
1423 locale: impl Into<SetLocaleOverrideParams>,
1424 ) -> Result<&Self> {
1425 self.execute(locale.into()).await?;
1426 Ok(self)
1427 }
1428
1429 /// Overrides default viewport
1430 pub async fn emulate_viewport(
1431 &self,
1432 viewport: impl Into<SetDeviceMetricsOverrideParams>,
1433 ) -> Result<&Self> {
1434 self.execute(viewport.into()).await?;
1435 Ok(self)
1436 }
1437
1438 /// Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.
1439 pub async fn emulate_geolocation(
1440 &self,
1441 geolocation: impl Into<SetGeolocationOverrideParams>,
1442 ) -> Result<&Self> {
1443 self.execute(geolocation.into()).await?;
1444 Ok(self)
1445 }
1446
1447 /// Reloads given page
1448 ///
1449 /// To reload ignoring cache run:
1450 /// ```no_run
1451 /// # use chromiumoxide::page::Page;
1452 /// # use chromiumoxide::error::Result;
1453 /// # use chromiumoxide_cdp::cdp::browser_protocol::page::ReloadParams;
1454 /// # async fn demo(page: Page) -> Result<()> {
1455 /// page.execute(ReloadParams::builder().ignore_cache(true).build()).await?;
1456 /// page.wait_for_navigation().await?;
1457 /// # Ok(())
1458 /// # }
1459 /// ```
1460 pub async fn reload(&self) -> Result<&Self> {
1461 self.execute(ReloadParams::default()).await?;
1462 self.wait_for_navigation().await
1463 }
1464
1465 /// Enables ServiceWorkers. Disabled by default.
1466 /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1467 pub async fn enable_service_workers(&self) -> Result<&Self> {
1468 self.execute(browser_protocol::service_worker::EnableParams::default())
1469 .await?;
1470 Ok(self)
1471 }
1472
1473 /// Disables ServiceWorker. Disabled by default.
1474 /// See https://chromedevtools.github.io/devtools-protocol/tot/ServiceWorker#method-enable
1475 pub async fn disable_service_workers(&self) -> Result<&Self> {
1476 self.execute(browser_protocol::service_worker::DisableParams::default())
1477 .await?;
1478 Ok(self)
1479 }
1480
1481 /// Enables Overlay domain notifications. Disabled by default.
1482 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1483 pub async fn enable_overlay(&self) -> Result<&Self> {
1484 self.execute(browser_protocol::overlay::EnableParams::default())
1485 .await?;
1486 Ok(self)
1487 }
1488
1489 /// Disables Overlay domain notifications. Disabled by default.
1490 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay#method-enable
1491 pub async fn disable_overlay(&self) -> Result<&Self> {
1492 self.execute(browser_protocol::overlay::DisableParams::default())
1493 .await?;
1494 Ok(self)
1495 }
1496
1497 /// Enables Overlay domain paint rectangles. Disabled by default.
1498 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1499 pub async fn enable_paint_rectangles(&self) -> Result<&Self> {
1500 self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1501 true,
1502 ))
1503 .await?;
1504 Ok(self)
1505 }
1506
1507 /// Disabled Overlay domain paint rectangles. Disabled by default.
1508 /// See https://chromedevtools.github.io/devtools-protocol/tot/Overlay/#method-setShowPaintRects
1509 pub async fn disable_paint_rectangles(&self) -> Result<&Self> {
1510 self.execute(browser_protocol::overlay::SetShowPaintRectsParams::new(
1511 false,
1512 ))
1513 .await?;
1514 Ok(self)
1515 }
1516
1517 /// Enables log domain. Enabled by default.
1518 ///
1519 /// Sends the entries collected so far to the client by means of the
1520 /// entryAdded notification.
1521 ///
1522 /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-enable
1523 pub async fn enable_log(&self) -> Result<&Self> {
1524 self.execute(browser_protocol::log::EnableParams::default())
1525 .await?;
1526 Ok(self)
1527 }
1528
1529 /// Disables log domain
1530 ///
1531 /// Prevents further log entries from being reported to the client
1532 ///
1533 /// See https://chromedevtools.github.io/devtools-protocol/tot/Log#method-disable
1534 pub async fn disable_log(&self) -> Result<&Self> {
1535 self.execute(browser_protocol::log::DisableParams::default())
1536 .await?;
1537 Ok(self)
1538 }
1539
1540 /// Enables runtime domain. Activated by default.
1541 pub async fn enable_runtime(&self) -> Result<&Self> {
1542 self.execute(js_protocol::runtime::EnableParams::default())
1543 .await?;
1544 Ok(self)
1545 }
1546
1547 /// Disables runtime domain.
1548 pub async fn disable_runtime(&self) -> Result<&Self> {
1549 self.execute(js_protocol::runtime::DisableParams::default())
1550 .await?;
1551 Ok(self)
1552 }
1553
1554 /// Enables Debugger. Enabled by default.
1555 pub async fn enable_debugger(&self) -> Result<&Self> {
1556 self.execute(js_protocol::debugger::EnableParams::default())
1557 .await?;
1558 Ok(self)
1559 }
1560
1561 /// Disables Debugger.
1562 pub async fn disable_debugger(&self) -> Result<&Self> {
1563 self.execute(js_protocol::debugger::DisableParams::default())
1564 .await?;
1565 Ok(self)
1566 }
1567
1568 // Enables DOM agent
1569 pub async fn enable_dom(&self) -> Result<&Self> {
1570 self.execute(browser_protocol::dom::EnableParams::default())
1571 .await?;
1572 Ok(self)
1573 }
1574
1575 // Disables DOM agent
1576 pub async fn disable_dom(&self) -> Result<&Self> {
1577 self.execute(browser_protocol::dom::DisableParams::default())
1578 .await?;
1579 Ok(self)
1580 }
1581
1582 // Enables the CSS agent
1583 pub async fn enable_css(&self) -> Result<&Self> {
1584 self.execute(browser_protocol::css::EnableParams::default())
1585 .await?;
1586 Ok(self)
1587 }
1588
1589 // Disables the CSS agent
1590 pub async fn disable_css(&self) -> Result<&Self> {
1591 self.execute(browser_protocol::css::DisableParams::default())
1592 .await?;
1593 Ok(self)
1594 }
1595
1596 /// Block urls from networking.
1597 ///
1598 /// Prevents further networking
1599 ///
1600 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1601 pub async fn set_blocked_urls(&self, urls: Vec<String>) -> Result<&Self> {
1602 self.execute(SetBlockedUrLsParams::new(urls)).await?;
1603 Ok(self)
1604 }
1605
1606 /// Block all urls from networking.
1607 ///
1608 /// Prevents further networking
1609 ///
1610 /// See https://chromedevtools.github.io/devtools-protocol/tot/Network#method-setBlockedURLs
1611 pub async fn block_all_urls(&self) -> Result<&Self> {
1612 self.execute(SetBlockedUrLsParams::new(vec!["*".into()]))
1613 .await?;
1614 Ok(self)
1615 }
1616
1617 /// Activates (focuses) the target.
1618 pub async fn activate(&self) -> Result<&Self> {
1619 self.inner.activate().await?;
1620 Ok(self)
1621 }
1622
1623 /// Returns all cookies that match the tab's current URL.
1624 pub async fn get_cookies(&self) -> Result<Vec<Cookie>> {
1625 Ok(self
1626 .execute(GetCookiesParams::default())
1627 .await?
1628 .result
1629 .cookies)
1630 }
1631
1632 /// Set a single cookie
1633 ///
1634 /// This fails if the cookie's url or if not provided, the page's url is
1635 /// `about:blank` or a `data:` url.
1636 ///
1637 /// # Example
1638 /// ```no_run
1639 /// # use chromiumoxide::page::Page;
1640 /// # use chromiumoxide::error::Result;
1641 /// # use chromiumoxide_cdp::cdp::browser_protocol::network::CookieParam;
1642 /// # async fn demo(page: Page) -> Result<()> {
1643 /// page.set_cookie(CookieParam::new("Cookie-name", "Cookie-value")).await?;
1644 /// # Ok(())
1645 /// # }
1646 /// ```
1647 pub async fn set_cookie(&self, cookie: impl Into<CookieParam>) -> Result<&Self> {
1648 let mut cookie = cookie.into();
1649 if let Some(url) = cookie.url.as_ref() {
1650 validate_cookie_url(url)?;
1651 } else {
1652 let url = self
1653 .url()
1654 .await?
1655 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1656 validate_cookie_url(&url)?;
1657 if url.starts_with("http") {
1658 cookie.url = Some(url);
1659 }
1660 }
1661 self.execute(DeleteCookiesParams::from_cookie(&cookie))
1662 .await?;
1663 self.execute(SetCookiesParams::new(vec![cookie])).await?;
1664 Ok(self)
1665 }
1666
1667 /// Set all the cookies
1668 pub async fn set_cookies(&self, mut cookies: Vec<CookieParam>) -> Result<&Self> {
1669 let url = self
1670 .url()
1671 .await?
1672 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1673 let is_http = url.starts_with("http");
1674 if !is_http {
1675 validate_cookie_url(&url)?;
1676 }
1677
1678 for cookie in &mut cookies {
1679 if let Some(url) = cookie.url.as_ref() {
1680 validate_cookie_url(url)?;
1681 } else if is_http {
1682 cookie.url = Some(url.clone());
1683 }
1684 }
1685 self.delete_cookies_unchecked(cookies.iter().map(DeleteCookiesParams::from_cookie))
1686 .await?;
1687
1688 self.execute(SetCookiesParams::new(cookies)).await?;
1689 Ok(self)
1690 }
1691
1692 /// Delete a single cookie
1693 pub async fn delete_cookie(&self, cookie: impl Into<DeleteCookiesParams>) -> Result<&Self> {
1694 let mut cookie = cookie.into();
1695 if cookie.url.is_none() {
1696 let url = self
1697 .url()
1698 .await?
1699 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1700 if url.starts_with("http") {
1701 cookie.url = Some(url);
1702 }
1703 }
1704 self.execute(cookie).await?;
1705 Ok(self)
1706 }
1707
1708 /// Delete all the cookies
1709 pub async fn delete_cookies(&self, mut cookies: Vec<DeleteCookiesParams>) -> Result<&Self> {
1710 let mut url: Option<(String, bool)> = None;
1711 for cookie in &mut cookies {
1712 if cookie.url.is_none() {
1713 if let Some((url, is_http)) = url.as_ref() {
1714 if *is_http {
1715 cookie.url = Some(url.clone())
1716 }
1717 } else {
1718 let page_url = self
1719 .url()
1720 .await?
1721 .ok_or_else(|| CdpError::msg("Page url not found"))?;
1722 let is_http = page_url.starts_with("http");
1723 if is_http {
1724 cookie.url = Some(page_url.clone())
1725 }
1726 url = Some((page_url, is_http));
1727 }
1728 }
1729 }
1730 self.delete_cookies_unchecked(cookies.into_iter()).await?;
1731 Ok(self)
1732 }
1733
1734 /// Convenience method that prevents another channel roundtrip to get the
1735 /// url and validate it
1736 async fn delete_cookies_unchecked(
1737 &self,
1738 cookies: impl Iterator<Item = DeleteCookiesParams>,
1739 ) -> Result<&Self> {
1740 // NOTE: the buffer size is arbitrary
1741 let mut cmds = stream::iter(cookies.into_iter().map(|cookie| self.execute(cookie)))
1742 .buffer_unordered(5);
1743 while let Some(resp) = cmds.next().await {
1744 resp?;
1745 }
1746 Ok(self)
1747 }
1748
1749 /// Returns the title of the document.
1750 pub async fn get_title(&self) -> Result<Option<String>> {
1751 let result = self.evaluate("document.title").await?;
1752
1753 let title: String = result.into_value()?;
1754
1755 if title.is_empty() {
1756 Ok(None)
1757 } else {
1758 Ok(Some(title))
1759 }
1760 }
1761
1762 /// Retrieve current values of run-time metrics.
1763 pub async fn metrics(&self) -> Result<Vec<Metric>> {
1764 Ok(self
1765 .execute(GetMetricsParams::default())
1766 .await?
1767 .result
1768 .metrics)
1769 }
1770
1771 /// Returns metrics relating to the layout of the page
1772 pub async fn layout_metrics(&self) -> Result<GetLayoutMetricsReturns> {
1773 self.inner.layout_metrics().await
1774 }
1775
1776 /// This evaluates strictly as expression.
1777 ///
1778 /// Same as `Page::evaluate` but no fallback or any attempts to detect
1779 /// whether the expression is actually a function. However you can
1780 /// submit a function evaluation string:
1781 ///
1782 /// # Example Evaluate function call as expression
1783 ///
1784 /// This will take the arguments `(1,2)` and will call the function
1785 ///
1786 /// ```no_run
1787 /// # use chromiumoxide::page::Page;
1788 /// # use chromiumoxide::error::Result;
1789 /// # async fn demo(page: Page) -> Result<()> {
1790 /// let sum: usize = page
1791 /// .evaluate_expression("((a,b) => {return a + b;})(1,2)")
1792 /// .await?
1793 /// .into_value()?;
1794 /// assert_eq!(sum, 3);
1795 /// # Ok(())
1796 /// # }
1797 /// ```
1798 pub async fn evaluate_expression(
1799 &self,
1800 evaluate: impl Into<EvaluateParams>,
1801 ) -> Result<EvaluationResult> {
1802 self.inner.evaluate_expression(evaluate).await
1803 }
1804
1805 /// Evaluates an expression or function in the page's context and returns
1806 /// the result.
1807 ///
1808 /// In contrast to `Page::evaluate_expression` this is capable of handling
1809 /// function calls and expressions alike. This takes anything that is
1810 /// `Into<Evaluation>`. When passing a `String` or `str`, this will try to
1811 /// detect whether it is a function or an expression. JS function detection
1812 /// is not very sophisticated but works for general cases (`(async)
1813 /// functions` and arrow functions). If you want a string statement
1814 /// specifically evaluated as expression or function either use the
1815 /// designated functions `Page::evaluate_function` or
1816 /// `Page::evaluate_expression` or use the proper parameter type for
1817 /// `Page::execute`: `EvaluateParams` for strict expression evaluation or
1818 /// `CallFunctionOnParams` for strict function evaluation.
1819 ///
1820 /// If you don't trust the js function detection and are not sure whether
1821 /// the statement is an expression or of type function (arrow functions: `()
1822 /// => {..}`), you should pass it as `EvaluateParams` and set the
1823 /// `EvaluateParams::eval_as_function_fallback` option. This will first
1824 /// try to evaluate it as expression and if the result comes back
1825 /// evaluated as `RemoteObjectType::Function` it will submit the
1826 /// statement again but as function:
1827 ///
1828 /// # Example Evaluate function statement as expression with fallback
1829 /// option
1830 ///
1831 /// ```no_run
1832 /// # use chromiumoxide::page::Page;
1833 /// # use chromiumoxide::error::Result;
1834 /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{EvaluateParams, RemoteObjectType};
1835 /// # async fn demo(page: Page) -> Result<()> {
1836 /// let eval = EvaluateParams::builder().expression("() => {return 42;}");
1837 /// // this will fail because the `EvaluationResult` returned by the browser will be
1838 /// // of type `Function`
1839 /// let result = page
1840 /// .evaluate(eval.clone().build().unwrap())
1841 /// .await?;
1842 /// assert_eq!(result.object().r#type, RemoteObjectType::Function);
1843 /// assert!(result.into_value::<usize>().is_err());
1844 ///
1845 /// // This will also fail on the first try but it detects that the browser evaluated the
1846 /// // statement as function and then evaluate it again but as function
1847 /// let sum: usize = page
1848 /// .evaluate(eval.eval_as_function_fallback(true).build().unwrap())
1849 /// .await?
1850 /// .into_value()?;
1851 /// # Ok(())
1852 /// # }
1853 /// ```
1854 ///
1855 /// # Example Evaluate basic expression
1856 /// ```no_run
1857 /// # use chromiumoxide::page::Page;
1858 /// # use chromiumoxide::error::Result;
1859 /// # async fn demo(page: Page) -> Result<()> {
1860 /// let sum:usize = page.evaluate("1 + 2").await?.into_value()?;
1861 /// assert_eq!(sum, 3);
1862 /// # Ok(())
1863 /// # }
1864 /// ```
1865 pub async fn evaluate(&self, evaluate: impl Into<Evaluation>) -> Result<EvaluationResult> {
1866 match evaluate.into() {
1867 Evaluation::Expression(mut expr) => {
1868 if expr.context_id.is_none() {
1869 expr.context_id = self.execution_context().await?;
1870 }
1871 let fallback = expr.eval_as_function_fallback.and_then(|p| {
1872 if p {
1873 Some(expr.clone())
1874 } else {
1875 None
1876 }
1877 });
1878 let res = self.evaluate_expression(expr).await?;
1879
1880 if res.object().r#type == RemoteObjectType::Function {
1881 // expression was actually a function
1882 if let Some(fallback) = fallback {
1883 return self.evaluate_function(fallback).await;
1884 }
1885 }
1886 Ok(res)
1887 }
1888 Evaluation::Function(fun) => Ok(self.evaluate_function(fun).await?),
1889 }
1890 }
1891
1892 /// Eexecutes a function withinthe page's context and returns the result.
1893 ///
1894 /// # Example Evaluate a promise
1895 /// This will wait until the promise resolves and then returns the result.
1896 /// ```no_run
1897 /// # use chromiumoxide::page::Page;
1898 /// # use chromiumoxide::error::Result;
1899 /// # async fn demo(page: Page) -> Result<()> {
1900 /// let sum:usize = page.evaluate_function("() => Promise.resolve(1 + 2)").await?.into_value()?;
1901 /// assert_eq!(sum, 3);
1902 /// # Ok(())
1903 /// # }
1904 /// ```
1905 ///
1906 /// # Example Evaluate an async function
1907 /// ```no_run
1908 /// # use chromiumoxide::page::Page;
1909 /// # use chromiumoxide::error::Result;
1910 /// # async fn demo(page: Page) -> Result<()> {
1911 /// let val:usize = page.evaluate_function("async function() {return 42;}").await?.into_value()?;
1912 /// assert_eq!(val, 42);
1913 /// # Ok(())
1914 /// # }
1915 /// ```
1916 /// # Example Construct a function call
1917 ///
1918 /// ```no_run
1919 /// # use chromiumoxide::page::Page;
1920 /// # use chromiumoxide::error::Result;
1921 /// # use chromiumoxide_cdp::cdp::js_protocol::runtime::{CallFunctionOnParams, CallArgument};
1922 /// # async fn demo(page: Page) -> Result<()> {
1923 /// let call = CallFunctionOnParams::builder()
1924 /// .function_declaration(
1925 /// "(a,b) => { return a + b;}"
1926 /// )
1927 /// .argument(
1928 /// CallArgument::builder()
1929 /// .value(serde_json::json!(1))
1930 /// .build(),
1931 /// )
1932 /// .argument(
1933 /// CallArgument::builder()
1934 /// .value(serde_json::json!(2))
1935 /// .build(),
1936 /// )
1937 /// .build()
1938 /// .unwrap();
1939 /// let sum:usize = page.evaluate_function(call).await?.into_value()?;
1940 /// assert_eq!(sum, 3);
1941 /// # Ok(())
1942 /// # }
1943 /// ```
1944 pub async fn evaluate_function(
1945 &self,
1946 evaluate: impl Into<CallFunctionOnParams>,
1947 ) -> Result<EvaluationResult> {
1948 self.inner.evaluate_function(evaluate).await
1949 }
1950
1951 /// Returns the default execution context identifier of this page that
1952 /// represents the context for JavaScript execution.
1953 pub async fn execution_context(&self) -> Result<Option<ExecutionContextId>> {
1954 self.inner.execution_context().await
1955 }
1956
1957 /// Returns the secondary execution context identifier of this page that
1958 /// represents the context for JavaScript execution for manipulating the
1959 /// DOM.
1960 ///
1961 /// See `Page::set_contents`
1962 pub async fn secondary_execution_context(&self) -> Result<Option<ExecutionContextId>> {
1963 self.inner.secondary_execution_context().await
1964 }
1965
1966 pub async fn frame_execution_context(
1967 &self,
1968 frame_id: FrameId,
1969 ) -> Result<Option<ExecutionContextId>> {
1970 self.inner.frame_execution_context(frame_id).await
1971 }
1972
1973 pub async fn frame_secondary_execution_context(
1974 &self,
1975 frame_id: FrameId,
1976 ) -> Result<Option<ExecutionContextId>> {
1977 self.inner.frame_secondary_execution_context(frame_id).await
1978 }
1979
1980 /// Evaluates given script in every frame upon creation (before loading
1981 /// frame's scripts)
1982 pub async fn evaluate_on_new_document(
1983 &self,
1984 script: impl Into<AddScriptToEvaluateOnNewDocumentParams>,
1985 ) -> Result<ScriptIdentifier> {
1986 Ok(self.execute(script.into()).await?.result.identifier)
1987 }
1988
1989 /// Set the content of the frame.
1990 ///
1991 /// # Example
1992 /// ```no_run
1993 /// # use chromiumoxide::page::Page;
1994 /// # use chromiumoxide::error::Result;
1995 /// # async fn demo(page: Page) -> Result<()> {
1996 /// page.set_content("<body>
1997 /// <h1>This was set via chromiumoxide</h1>
1998 /// </body>").await?;
1999 /// # Ok(())
2000 /// # }
2001 /// ```
2002 pub async fn set_content(&self, html: impl AsRef<str>) -> Result<&Self> {
2003 let mut call = CallFunctionOnParams::builder()
2004 .function_declaration(
2005 "(html) => {
2006 document.open();
2007 document.write(html);
2008 document.close();
2009 }",
2010 )
2011 .argument(
2012 CallArgument::builder()
2013 .value(serde_json::json!(html.as_ref()))
2014 .build(),
2015 )
2016 .build()
2017 .unwrap();
2018
2019 call.execution_context_id = self
2020 .inner
2021 .execution_context_for_world(None, DOMWorldKind::Secondary)
2022 .await?;
2023
2024 self.evaluate_function(call).await?;
2025 // relying that document.open() will reset frame lifecycle with "init"
2026 // lifecycle event. @see https://crrev.com/608658
2027 self.wait_for_navigation().await
2028 }
2029
2030 /// Returns the HTML content of the page.
2031 pub async fn content(&self) -> Result<String> {
2032 Ok(self.evaluate(OUTER_HTML).await?.into_value()?)
2033 }
2034
2035 /// Returns the HTML content of the page
2036 pub async fn content_bytes(&self) -> Result<Vec<u8>> {
2037 Ok(self.evaluate(OUTER_HTML).await?.into_bytes()?)
2038 }
2039
2040 /// Returns the full serialized content of the page (HTML or XML)
2041 pub async fn content_bytes_xml(&self) -> Result<Vec<u8>> {
2042 Ok(self.evaluate(FULL_XML_SERIALIZER_JS).await?.into_bytes()?)
2043 }
2044
2045 /// Returns the HTML outer html of the page
2046 pub async fn outer_html_bytes(&self) -> Result<Vec<u8>> {
2047 Ok(self.outer_html().await?.into())
2048 }
2049
2050 /// Enable Chrome's experimental ad filter on all sites.
2051 pub async fn set_ad_blocking_enabled(&self, enabled: bool) -> Result<&Self> {
2052 self.execute(SetAdBlockingEnabledParams::new(enabled))
2053 .await?;
2054 Ok(self)
2055 }
2056
2057 /// Start to screencast a frame.
2058 pub async fn start_screencast(
2059 &self,
2060 params: impl Into<StartScreencastParams>,
2061 ) -> Result<&Self> {
2062 self.execute(params.into()).await?;
2063 Ok(self)
2064 }
2065
2066 /// Acknowledges that a screencast frame has been received by the frontend.
2067 pub async fn ack_screencast(
2068 &self,
2069 params: impl Into<ScreencastFrameAckParams>,
2070 ) -> Result<&Self> {
2071 self.execute(params.into()).await?;
2072 Ok(self)
2073 }
2074
2075 /// Stop screencast a frame.
2076 pub async fn stop_screencast(&self, params: impl Into<StopScreencastParams>) -> Result<&Self> {
2077 self.execute(params.into()).await?;
2078 Ok(self)
2079 }
2080
2081 /// Returns source for the script with given id.
2082 ///
2083 /// Debugger must be enabled.
2084 pub async fn get_script_source(&self, script_id: impl Into<String>) -> Result<String> {
2085 Ok(self
2086 .execute(GetScriptSourceParams::new(ScriptId::from(script_id.into())))
2087 .await?
2088 .result
2089 .script_source)
2090 }
2091}
2092
2093impl From<Arc<PageInner>> for Page {
2094 fn from(inner: Arc<PageInner>) -> Self {
2095 Self { inner }
2096 }
2097}
2098
2099pub(crate) fn validate_cookie_url(url: &str) -> Result<()> {
2100 if url.starts_with("data:") {
2101 Err(CdpError::msg("Data URL page can not have cookie"))
2102 } else if url == "about:blank" {
2103 Err(CdpError::msg("Blank page can not have cookie"))
2104 } else {
2105 Ok(())
2106 }
2107}
2108
2109/// Page screenshot parameters with extra options.
2110#[derive(Debug, Default)]
2111pub struct ScreenshotParams {
2112 /// Chrome DevTools Protocol screenshot options.
2113 pub cdp_params: CaptureScreenshotParams,
2114 /// Take full page screenshot.
2115 pub full_page: Option<bool>,
2116 /// Make the background transparent (png only).
2117 pub omit_background: Option<bool>,
2118}
2119
2120impl ScreenshotParams {
2121 pub fn builder() -> ScreenshotParamsBuilder {
2122 Default::default()
2123 }
2124
2125 pub(crate) fn full_page(&self) -> bool {
2126 self.full_page.unwrap_or(false)
2127 }
2128
2129 pub(crate) fn omit_background(&self) -> bool {
2130 self.omit_background.unwrap_or(false)
2131 && self
2132 .cdp_params
2133 .format
2134 .as_ref()
2135 .map_or(true, |f| f == &CaptureScreenshotFormat::Png)
2136 }
2137}
2138
2139/// Page screenshot parameters builder with extra options.
2140#[derive(Debug, Default)]
2141pub struct ScreenshotParamsBuilder {
2142 cdp_params: CaptureScreenshotParams,
2143 full_page: Option<bool>,
2144 omit_background: Option<bool>,
2145}
2146
2147impl ScreenshotParamsBuilder {
2148 /// Image compression format (defaults to png).
2149 pub fn format(mut self, format: impl Into<CaptureScreenshotFormat>) -> Self {
2150 self.cdp_params.format = Some(format.into());
2151 self
2152 }
2153
2154 /// Compression quality from range [0..100] (jpeg only).
2155 pub fn quality(mut self, quality: impl Into<i64>) -> Self {
2156 self.cdp_params.quality = Some(quality.into());
2157 self
2158 }
2159
2160 /// Capture the screenshot of a given region only.
2161 pub fn clip(mut self, clip: impl Into<Viewport>) -> Self {
2162 self.cdp_params.clip = Some(clip.into());
2163 self
2164 }
2165
2166 /// Capture the screenshot from the surface, rather than the view (defaults to true).
2167 pub fn from_surface(mut self, from_surface: impl Into<bool>) -> Self {
2168 self.cdp_params.from_surface = Some(from_surface.into());
2169 self
2170 }
2171
2172 /// Capture the screenshot beyond the viewport (defaults to false).
2173 pub fn capture_beyond_viewport(mut self, capture_beyond_viewport: impl Into<bool>) -> Self {
2174 self.cdp_params.capture_beyond_viewport = Some(capture_beyond_viewport.into());
2175 self
2176 }
2177
2178 /// Full page screen capture.
2179 pub fn full_page(mut self, full_page: impl Into<bool>) -> Self {
2180 self.full_page = Some(full_page.into());
2181 self
2182 }
2183
2184 /// Make the background transparent (png only)
2185 pub fn omit_background(mut self, omit_background: impl Into<bool>) -> Self {
2186 self.omit_background = Some(omit_background.into());
2187 self
2188 }
2189
2190 pub fn build(self) -> ScreenshotParams {
2191 ScreenshotParams {
2192 cdp_params: self.cdp_params,
2193 full_page: self.full_page,
2194 omit_background: self.omit_background,
2195 }
2196 }
2197}
2198
2199impl From<CaptureScreenshotParams> for ScreenshotParams {
2200 fn from(cdp_params: CaptureScreenshotParams) -> Self {
2201 Self {
2202 cdp_params,
2203 ..Default::default()
2204 }
2205 }
2206}
2207
2208#[derive(Debug, Clone, Copy, Default)]
2209pub enum MediaTypeParams {
2210 /// Default CSS media type behavior for page and print
2211 #[default]
2212 Null,
2213 /// Force screen CSS media type for page and print
2214 Screen,
2215 /// Force print CSS media type for page and print
2216 Print,
2217}
2218impl From<MediaTypeParams> for String {
2219 fn from(media_type: MediaTypeParams) -> Self {
2220 match media_type {
2221 MediaTypeParams::Null => "null".to_string(),
2222 MediaTypeParams::Screen => "screen".to_string(),
2223 MediaTypeParams::Print => "print".to_string(),
2224 }
2225 }
2226}