Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
67 changes: 67 additions & 0 deletions chromiumoxide_types/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,70 @@ impl From<String> for Binary {
Self(expr)
}
}

/// 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<i64>) -> Self {
self.click_count = count.into();
self
}

/// Builds the [`ClickOptions`] instance.
pub fn build(self) -> ClickOptions {
ClickOptions {
click_count: self.click_count,
}
}
}
14 changes: 14 additions & 0 deletions src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -271,6 +272,19 @@ impl Element {
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_with(center, options).await?;
Ok(self)
}

/// Type the input
///
/// # Example type text into an input element
Expand Down
12 changes: 11 additions & 1 deletion src/handler/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,11 +191,21 @@ 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();
self.click_with(point, default_opts).await
}

/// 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)
.button(MouseButton::Left)
.click_count(1);
.click_count(options.click_count);

self.move_mouse(point)
.await?
Expand Down
65 changes: 62 additions & 3 deletions src/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand All @@ -521,9 +521,28 @@ 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;
Expand Down Expand Up @@ -561,6 +580,46 @@ impl Page {
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_with(point, options).await?;
Ok(self)
}

/// Dispatches a `mousemove` event and moves the mouse to the position of
/// the `point` where `Point.x` is the horizontal position of the mouse and
/// `Point.y` the vertical position of the mouse.
Expand Down