From fc214c9553835e84ca0ddc4de20903f603a9c36c Mon Sep 17 00:00:00 2001 From: vrdons Date: Tue, 13 Jan 2026 23:57:52 +0300 Subject: [PATCH 1/4] add click_with API for custom click behavior --- CHANGELOG.md | 1 + chromiumoxide_types/src/lib.rs | 47 ++++++++++++++++++++++ src/element.rs | 16 +++++++- src/handler/page.rs | 4 +- src/page.rs | 72 ++++++++++++++++++++++++++++++++-- 5 files changed, 133 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a339c689..bfcc2af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `add_init_script` to `Page` for scripts before navigation +- Add `click_with` to `Page` and `Element` for custom click behavior ## [0.8.0] 2025-11-28 diff --git a/chromiumoxide_types/src/lib.rs b/chromiumoxide_types/src/lib.rs index ce6f022e..d90eef7f 100644 --- a/chromiumoxide_types/src/lib.rs +++ b/chromiumoxide_types/src/lib.rs @@ -266,3 +266,50 @@ impl From for Binary { Self(expr) } } + +#[derive(Clone, Debug)] +pub struct ClickOptions { + pub click_count: i64, +} + +impl Default for ClickOptions { + fn default() -> Self { + Self { click_count: 1 } + } +} + +impl ClickOptions { + pub fn new() -> Self { + Self::default() + } + pub fn builder() -> ClickOptionsBuilder { + ClickOptionsBuilder::default() + } +} + +#[derive(Clone, Debug)] +pub struct ClickOptionsBuilder { + click_count: i64, +} + +impl Default for ClickOptionsBuilder { + fn default() -> Self { + Self { click_count: 1 } + } +} + +impl ClickOptionsBuilder { + pub fn new() -> Self { + Self::default() + } + pub fn click_count(mut self, count: impl Into) -> Self { + self.click_count = count.into(); + self + } + + pub fn build(self) -> ClickOptions { + ClickOptions { + click_count: self.click_count, + } + } +} diff --git a/src/element.rs b/src/element.rs index 9fcc0c78..66994cbe 100644 --- a/src/element.rs +++ b/src/element.rs @@ -4,6 +4,7 @@ use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; +use chromiumoxide_types::ClickOptions; use futures::{future, Future, FutureExt, Stream}; use chromiumoxide_cdp::cdp::browser_protocol::dom::{ @@ -267,7 +268,20 @@ impl Element { /// not exist anymore. pub async fn click(&self) -> Result<&Self> { let center = self.scroll_into_view().await?.clickable_point().await?; - self.tab.click(center).await?; + self.tab.click(center, ClickOptions::default()).await?; + Ok(self) + } + + /// Clicks the element using the provided [`ClickOptions`]. + /// + /// This behaves the same as [`click()`], but allows customizing + /// click behavior such as click count. + /// + /// Note that if the click triggers a navigation, this element + /// may no longer exist afterwards. + pub async fn click_with(&self,options: ClickOptions) -> Result<&Self> { + let center = self.scroll_into_view().await?.clickable_point().await?; + self.tab.click(center, options).await?; Ok(self) } diff --git a/src/handler/page.rs b/src/handler/page.rs index 294317da..75bdd4e0 100644 --- a/src/handler/page.rs +++ b/src/handler/page.rs @@ -190,12 +190,12 @@ impl PageInner { } /// Performs a mouse click event at the point's location - pub async fn click(&self, point: Point) -> Result<&Self> { + pub async fn click(&self, point: Point, options: chromiumoxide_types::ClickOptions) -> Result<&Self> { let cmd = DispatchMouseEventParams::builder() .x(point.x) .y(point.y) .button(MouseButton::Left) - .click_count(1); + .click_count(options.click_count); self.move_mouse(point) .await? diff --git a/src/page.rs b/src/page.rs index df8da0b2..e8504886 100644 --- a/src/page.rs +++ b/src/page.rs @@ -507,7 +507,7 @@ impl Page { /// immediately loaded when `click()` resolves. To wait until navigation is /// finished an additional `wait_for_navigation()` is required: /// - /// # Example + /// # Examples /// /// Trigger a navigation and wait until the triggered navigation is finished /// @@ -521,10 +521,29 @@ impl Page { /// # } /// ``` /// - /// # Example /// - /// Perform custom click + /// Use [`click_with()`] to perform a custom click: + /// + /// ```no_run + /// # use chromiumoxide::page::Page; + /// # use chromiumoxide::error::Result; + /// # use chromiumoxide::layout::Point; + /// # use chromiumoxide::types::ClickOptions; + /// # async fn demo(page: Page, point: Point) -> Result<()> { + /// let options = ClickOptions::builder() + /// .click_count(2) + /// .build(); + /// + /// page.click_with(point, options).await?; + /// # Ok(()) + /// # } + /// ``` + /// + /// ## Advanced /// + /// For advanced use cases, the same behavior can be achieved manually by + /// issuing `DispatchMouseEventParams` commands directly via the CDP API. + /// /// ```no_run /// # use chromiumoxide::page::Page; /// # use chromiumoxide::error::Result; @@ -557,7 +576,51 @@ impl Page { /// # } /// ``` pub async fn click(&self, point: Point) -> Result<&Self> { - self.inner.click(point).await?; + self.inner.click(point, ClickOptions::default()).await?; + Ok(self) + } + + /// Performs a mouse click event at the point's location using the provided + /// [`ClickOptions`]. + /// + /// This behaves the same as [`click()`], but allows customizing click behavior + /// such as click count or other click-related options. + /// + /// The point is scrolled into view first, then the corresponding + /// `DispatchMouseEventParams` commands are issued according to the supplied + /// options. + /// + /// Bear in mind that if `click_with()` triggers a navigation, the new page is + /// not immediately loaded when this function resolves. To wait until navigation + /// is finished, an additional [`wait_for_navigation()`] is required. + /// + /// # Example + /// + /// Perform a double click using [`ClickOptions`] + /// + /// ```no_run + /// # use chromiumoxide::page::Page; + /// # use chromiumoxide::error::Result; + /// # use chromiumoxide::layout::Point; + /// # use chromiumoxide::types::ClickOptions; + /// # async fn demo(page: Page, point: Point) -> Result<()> { + /// let options = ClickOptions::builder() + /// .click_count(2) + /// .build(); + /// + /// page.click_with(point, options) + /// .await? + /// .wait_for_navigation() + /// .await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn click_with( + &self, + point: Point, + options: ClickOptions, + ) -> Result<&Self> { + self.inner.click(point, options).await?; Ok(self) } @@ -1411,3 +1474,4 @@ impl From for String { } } } + From f17889d492ca75f4491ec170ed6f4ae109eaeeaa Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 13:44:39 +0300 Subject: [PATCH 2/4] Add click_with to page handler --- src/element.rs | 6 +++--- src/handler/page.rs | 34 +++++++++++++++++++++++++++++++++- src/page.rs | 15 +++++---------- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/element.rs b/src/element.rs index 66994cbe..37f3632e 100644 --- a/src/element.rs +++ b/src/element.rs @@ -268,7 +268,7 @@ impl Element { /// not exist anymore. pub async fn click(&self) -> Result<&Self> { let center = self.scroll_into_view().await?.clickable_point().await?; - self.tab.click(center, ClickOptions::default()).await?; + self.tab.click(center).await?; Ok(self) } @@ -279,9 +279,9 @@ impl Element { /// /// Note that if the click triggers a navigation, this element /// may no longer exist afterwards. - pub async fn click_with(&self,options: ClickOptions) -> Result<&Self> { + pub async fn click_with(&self, options: ClickOptions) -> Result<&Self> { let center = self.scroll_into_view().await?.clickable_point().await?; - self.tab.click(center, options).await?; + self.tab.click_with(center, options).await?; Ok(self) } diff --git a/src/handler/page.rs b/src/handler/page.rs index 75bdd4e0..675ec7d3 100644 --- a/src/handler/page.rs +++ b/src/handler/page.rs @@ -190,7 +190,39 @@ impl PageInner { } /// Performs a mouse click event at the point's location - pub async fn click(&self, point: Point, options: chromiumoxide_types::ClickOptions) -> Result<&Self> { + pub async fn click(&self, point: Point) -> Result<&Self> { + let default_opts = chromiumoxide_types::ClickOptions::default(); + let cmd = DispatchMouseEventParams::builder() + .x(point.x) + .y(point.y) + .button(MouseButton::Left) + .click_count(default_opts.click_count); + + self.move_mouse(point) + .await? + .execute( + cmd.clone() + .r#type(DispatchMouseEventType::MousePressed) + .build() + .unwrap(), + ) + .await?; + + self.execute( + cmd.r#type(DispatchMouseEventType::MouseReleased) + .build() + .unwrap(), + ) + .await?; + Ok(self) + } + + /// Performs a mouse click event at the point's location with custom options + pub async fn click_with( + &self, + point: Point, + options: chromiumoxide_types::ClickOptions, + ) -> Result<&Self> { let cmd = DispatchMouseEventParams::builder() .x(point.x) .y(point.y) diff --git a/src/page.rs b/src/page.rs index e8504886..870afe89 100644 --- a/src/page.rs +++ b/src/page.rs @@ -523,7 +523,7 @@ impl Page { /// /// /// Use [`click_with()`] to perform a custom click: - /// + /// /// ```no_run /// # use chromiumoxide::page::Page; /// # use chromiumoxide::error::Result; @@ -543,7 +543,7 @@ impl Page { /// /// For advanced use cases, the same behavior can be achieved manually by /// issuing `DispatchMouseEventParams` commands directly via the CDP API. - /// + /// /// ```no_run /// # use chromiumoxide::page::Page; /// # use chromiumoxide::error::Result; @@ -576,7 +576,7 @@ impl Page { /// # } /// ``` pub async fn click(&self, point: Point) -> Result<&Self> { - self.inner.click(point, ClickOptions::default()).await?; + self.inner.click(point).await?; Ok(self) } @@ -615,12 +615,8 @@ impl Page { /// # Ok(()) /// # } /// ``` - pub async fn click_with( - &self, - point: Point, - options: ClickOptions, - ) -> Result<&Self> { - self.inner.click(point, options).await?; + pub async fn click_with(&self, point: Point, options: ClickOptions) -> Result<&Self> { + self.inner.click_with(point, options).await?; Ok(self) } @@ -1474,4 +1470,3 @@ impl From for String { } } } - From e38a20a052f9053157260b3c6d43fe3df3d0521a Mon Sep 17 00:00:00 2001 From: vrdons Date: Sat, 17 Jan 2026 13:44:55 +0300 Subject: [PATCH 3/4] Add documentation for ClickOptions --- chromiumoxide_types/src/lib.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/chromiumoxide_types/src/lib.rs b/chromiumoxide_types/src/lib.rs index d90eef7f..1c4c624e 100644 --- a/chromiumoxide_types/src/lib.rs +++ b/chromiumoxide_types/src/lib.rs @@ -267,46 +267,66 @@ impl From for Binary { } } +/// Options that control how a click action is performed. #[derive(Clone, Debug)] pub struct ClickOptions { + /// Number of times the click action should be executed. + /// + /// A value of `1` represents a single click. pub click_count: i64, } impl Default for ClickOptions { + /// Creates a [`ClickOptions`] instance with default values. + /// + /// Default values: + /// - `click_count`: `1` fn default() -> Self { Self { click_count: 1 } } } impl ClickOptions { + /// Creates a new [`ClickOptions`] using default values. pub fn new() -> Self { Self::default() } + + /// Creates a builder for constructing [`ClickOptions`]. pub fn builder() -> ClickOptionsBuilder { ClickOptionsBuilder::default() } } +/// Builder for [`ClickOptions`]. #[derive(Clone, Debug)] pub struct ClickOptionsBuilder { click_count: i64, } impl Default for ClickOptionsBuilder { + /// Creates a [`ClickOptionsBuilder`] with default values. + /// + /// Default values: + /// - `click_count`: `1` fn default() -> Self { Self { click_count: 1 } } } impl ClickOptionsBuilder { + /// Creates a new [`ClickOptionsBuilder`]. pub fn new() -> Self { Self::default() } + + /// Sets how many times the click action should be executed. pub fn click_count(mut self, count: impl Into) -> Self { self.click_count = count.into(); self } + /// Builds the [`ClickOptions`] instance. pub fn build(self) -> ClickOptions { ClickOptions { click_count: self.click_count, From 09c6e25ba16173502f0cd333047d568ae1371d4c Mon Sep 17 00:00:00 2001 From: Emile Fugulin Date: Fri, 20 Feb 2026 12:16:31 -0500 Subject: [PATCH 4/4] Remove code duplicates --- src/handler/page.rs | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/src/handler/page.rs b/src/handler/page.rs index 675ec7d3..505bf955 100644 --- a/src/handler/page.rs +++ b/src/handler/page.rs @@ -192,29 +192,7 @@ impl PageInner { /// Performs a mouse click event at the point's location pub async fn click(&self, point: Point) -> Result<&Self> { let default_opts = chromiumoxide_types::ClickOptions::default(); - let cmd = DispatchMouseEventParams::builder() - .x(point.x) - .y(point.y) - .button(MouseButton::Left) - .click_count(default_opts.click_count); - - self.move_mouse(point) - .await? - .execute( - cmd.clone() - .r#type(DispatchMouseEventType::MousePressed) - .build() - .unwrap(), - ) - .await?; - - self.execute( - cmd.r#type(DispatchMouseEventType::MouseReleased) - .build() - .unwrap(), - ) - .await?; - Ok(self) + self.click_with(point, default_opts).await } /// Performs a mouse click event at the point's location with custom options