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