8282//!
8383//! ### Custom Configuration
8484//!
85- //! ```rust
85+ //! ```no_run
8686//! use plotly_static::StaticExporterBuilder;
8787//!
8888//! let exporter = StaticExporterBuilder::default()
132132//! The library supports logging via the `log` crate. Enable it with
133133//! `env_logger`:
134134//!
135- //! ```rust
135+ //! ```no_run
136136//! use plotly_static::StaticExporterBuilder;
137137//!
138138//! // Initialize logging (typically done once at the start of your application)
150150//!
151151//! The library is designed to work safely in parallel environments:
152152//!
153- //! ```rust
153+ //! ```no_run
154154//! use plotly_static::{StaticExporterBuilder, ImageFormat};
155155//! use std::sync::atomic::{AtomicU32, Ordering};
156156//!
@@ -256,25 +256,44 @@ use fantoccini::{wd::Capabilities, ClientBuilder};
256256use log:: { debug, error, warn} ;
257257use serde:: Serialize ;
258258use serde_json:: map:: Map as JsonMap ;
259- use tokio:: runtime:: Runtime ;
260259use urlencoding:: encode;
261260use webdriver:: WebDriver ;
262261
263262use crate :: template:: { IMAGE_EXPORT_JS_SCRIPT , PDF_EXPORT_JS_SCRIPT } ;
264263
265- const DEFAULT_CAPS_ARRAY : [ & str ; 12 ] = [
264+ #[ cfg( feature = "chromedriver" ) ]
265+ const CHROME_DEFAULT_CAPS : [ & str ; 23 ] = [
266266 "--headless" ,
267267 "--no-sandbox" ,
268- "--disable-gpu" ,
268+ "--disable-gpu-sandbox " ,
269269 "--disable-dev-shm-usage" ,
270270 "--disable-extensions" ,
271271 "--disable-background-networking" ,
272272 "--disable-sync" ,
273273 "--disable-translate" ,
274- "--mute-audio" ,
275- "--safebrowsing-disable-auto-update" ,
276- "--ignore-certificate-errors" ,
277- "--log-level=1" ,
274+ "--disable-background-timer-throttling" ,
275+ "--disable-renderer-backgrounding" ,
276+ "--disable-features=VizDisplayCompositor" ,
277+ "--memory-pressure-off" ,
278+ // macOS-specific flags from choreographer
279+ "--enable-unsafe-swiftshader" ,
280+ "--use-mock-keychain" ,
281+ "--password-store=basic" ,
282+ "--disable-web-security" ,
283+ "--disable-breakpad" ,
284+ "--no-first-run" ,
285+ "--no-default-browser-check" ,
286+ // Additional flags for better PDF generation support
287+ "--disable-backgrounding-occluded-windows" ,
288+ "--disable-ipc-flooding-protection" ,
289+ "--enable-logging" ,
290+ "--v=1" ,
291+ ] ;
292+
293+ #[ cfg( feature = "geckodriver" ) ]
294+ const FIREFOX_DEFAULT_CAPS : [ & str ; 2 ] = [
295+ "-headless" , // Essential for headless operation (single dash for Firefox)
296+ "--no-remote" , // Prevents connecting to existing Firefox instances
278297] ;
279298
280299mod template;
@@ -383,7 +402,9 @@ struct PlotData<'a> {
383402///
384403/// # Examples
385404///
386- /// ```rust
405+ /// ```no_run
406+ /// // This example requires a running WebDriver (chromedriver/geckodriver) and a browser.
407+ /// // It cannot be run as a doc test.
387408/// use plotly_static::StaticExporterBuilder;
388409///
389410/// let exporter = StaticExporterBuilder::default()
@@ -425,7 +446,20 @@ impl Default for StaticExporterBuilder {
425446 webdriver_url : webdriver:: WEBDRIVER_URL . to_string ( ) ,
426447 spawn_webdriver : true ,
427448 offline_mode : false ,
428- webdriver_browser_caps : DEFAULT_CAPS_ARRAY . iter ( ) . map ( |s| s. to_string ( ) ) . collect ( ) ,
449+ webdriver_browser_caps : {
450+ #[ cfg( feature = "chromedriver" ) ]
451+ {
452+ CHROME_DEFAULT_CAPS . iter ( ) . map ( |s| s. to_string ( ) ) . collect ( )
453+ }
454+ #[ cfg( feature = "geckodriver" ) ]
455+ {
456+ FIREFOX_DEFAULT_CAPS . iter ( ) . map ( |s| s. to_string ( ) ) . collect ( )
457+ }
458+ #[ cfg( not( any( feature = "chromedriver" , feature = "geckodriver" ) ) ) ]
459+ {
460+ Vec :: new ( )
461+ }
462+ } ,
429463 }
430464 }
431465}
@@ -576,12 +610,20 @@ impl StaticExporterBuilder {
576610 pub fn build ( & self ) -> Result < StaticExporter > {
577611 let wd = self . create_webdriver ( ) ?;
578612
613+ let runtime = std:: sync:: Arc :: new (
614+ tokio:: runtime:: Builder :: new_multi_thread ( )
615+ . enable_all ( )
616+ . build ( )
617+ . expect ( "Failed to create Tokio runtime" ) ,
618+ ) ;
619+
579620 Ok ( StaticExporter {
580621 webdriver_port : self . webdriver_port ,
581- webdriver_url : self . webdriver_url . to_string ( ) ,
622+ webdriver_url : self . webdriver_url . clone ( ) ,
582623 webdriver : wd,
583624 offline_mode : self . offline_mode ,
584625 webdriver_browser_caps : self . webdriver_browser_caps . clone ( ) ,
626+ runtime,
585627 } )
586628 }
587629
@@ -649,6 +691,7 @@ pub struct StaticExporter {
649691 webdriver : WebDriver ,
650692 offline_mode : bool ,
651693 webdriver_browser_caps : Vec < String > ,
694+ runtime : std:: sync:: Arc < tokio:: runtime:: Runtime > ,
652695}
653696
654697impl Drop for StaticExporter {
@@ -663,8 +706,9 @@ impl Drop for StaticExporter {
663706 /// - Leaves externally managed WebDriver sessions running
664707 /// - Logs errors but doesn't panic if cleanup fails
665708 fn drop ( & mut self ) {
709+ // Stop the WebDriver process
666710 if let Err ( e) = self . webdriver . stop ( ) {
667- error ! ( "Failed to release WebDriver process : {e}" ) ;
711+ error ! ( "Failed to stop WebDriver: {e}" ) ;
668712 }
669713 }
670714}
@@ -835,7 +879,8 @@ impl StaticExporter {
835879
836880 fn static_export ( & mut self , plot : & PlotData < ' _ > ) -> Result < String > {
837881 let data_uri = template:: html_body ( self . offline_mode ) ;
838- Runtime :: new ( ) ?
882+ let runtime = self . runtime . clone ( ) ;
883+ runtime
839884 . block_on ( self . extract ( & data_uri, plot) )
840885 . with_context ( || "Failed to extract static image from browser session" )
841886 }
@@ -943,11 +988,9 @@ impl StaticExporter {
943988 // Define browser capabilities
944989 let mut caps = JsonMap :: new ( ) ;
945990 let mut browser_opts = JsonMap :: new ( ) ;
991+ let browser_args = self . webdriver_browser_caps . clone ( ) ;
946992
947- browser_opts. insert (
948- "args" . to_string ( ) ,
949- serde_json:: json!( self . webdriver_browser_caps. clone( ) ) ,
950- ) ;
993+ browser_opts. insert ( "args" . to_string ( ) , serde_json:: json!( browser_args) ) ;
951994
952995 caps. insert (
953996 "browserName" . to_string ( ) ,
@@ -962,6 +1005,19 @@ impl StaticExporter {
9621005
9631006 Ok ( caps)
9641007 }
1008+
1009+ /// Get diagnostic information about the underlying WebDriver process
1010+ ///
1011+ /// This method provides detailed information about the WebDriver process
1012+ /// for debugging purposes, including process status, port information,
1013+ /// and connection details.
1014+ ///
1015+ /// # Returns
1016+ ///
1017+ /// Returns a string with diagnostic information
1018+ pub fn get_webdriver_diagnostics ( & self ) -> String {
1019+ self . webdriver . get_diagnostics ( )
1020+ }
9651021}
9661022
9671023#[ cfg( test) ]
0 commit comments