@@ -266,7 +266,7 @@ use std::{println as error, println as warn, println as debug};
266266
267267use anyhow:: { anyhow, Context , Result } ;
268268use base64:: { engine:: general_purpose, Engine as _} ;
269- use fantoccini:: { wd:: Capabilities , ClientBuilder } ;
269+ use fantoccini:: { wd:: Capabilities , ClientBuilder , Client } ;
270270#[ cfg( not( test) ) ]
271271use log:: { debug, error, warn} ;
272272use serde:: Serialize ;
@@ -469,10 +469,15 @@ struct PlotData<'a> {
469469/// - Browser capabilities: Default Chrome/Firefox headless options
470470/// - Automatic WebDriver detection and connection reuse
471471pub struct StaticExporterBuilder {
472+ /// WebDriver server port (default: 4444)
472473 webdriver_port : u32 ,
474+ /// WebDriver server base URL (default: "http://localhost")
473475 webdriver_url : String ,
476+ /// Auto-spawn WebDriver if not running (default: true)
474477 spawn_webdriver : bool ,
478+ /// Use bundled JS libraries instead of CDN (default: false)
475479 offline_mode : bool ,
480+ /// Browser command-line flags (e.g., "--headless", "--no-sandbox")
476481 webdriver_browser_caps : Vec < String > ,
477482}
478483
@@ -675,6 +680,7 @@ impl StaticExporterBuilder {
675680 offline_mode : self . offline_mode ,
676681 webdriver_browser_caps : self . webdriver_browser_caps . clone ( ) ,
677682 runtime,
683+ webdriver_client : None ,
678684 } )
679685 }
680686
@@ -737,12 +743,26 @@ impl StaticExporterBuilder {
737743/// - Offline mode support
738744/// - Automatic WebDriver management
739745pub struct StaticExporter {
746+ /// WebDriver server port (default: 4444)
740747 webdriver_port : u32 ,
748+
749+ /// WebDriver server base URL (default: "http://localhost")
741750 webdriver_url : String ,
751+
752+ /// WebDriver process manager for spawning and cleanup
742753 webdriver : WebDriver ,
754+
755+ /// Use bundled JS libraries instead of CDN
743756 offline_mode : bool ,
757+
758+ /// Browser command-line flags (e.g., "--headless", "--no-sandbox")
744759 webdriver_browser_caps : Vec < String > ,
760+
761+ /// Tokio runtime for async operations
745762 runtime : std:: sync:: Arc < tokio:: runtime:: Runtime > ,
763+
764+ /// Cached WebDriver client for session reuse
765+ webdriver_client : Option < Client > ,
746766}
747767
748768impl Drop for StaticExporter {
@@ -757,6 +777,16 @@ impl Drop for StaticExporter {
757777 /// - Leaves externally managed WebDriver sessions running
758778 /// - Logs errors but doesn't panic if cleanup fails
759779 fn drop ( & mut self ) {
780+ // Close the WebDriver client if it exists
781+ if let Some ( client) = self . webdriver_client . take ( ) {
782+ let runtime = self . runtime . clone ( ) ;
783+ runtime. block_on ( async {
784+ if let Err ( e) = client. close ( ) . await {
785+ error ! ( "Failed to close WebDriver client: {}" , e) ;
786+ }
787+ } ) ;
788+ }
789+
760790 // Stop the WebDriver process
761791 if let Err ( e) = self . webdriver . stop ( ) {
762792 error ! ( "Failed to stop WebDriver: {e}" ) ;
@@ -941,11 +971,20 @@ impl StaticExporter {
941971 debug ! ( "Use WebDriver and headless browser to export static plot" ) ;
942972 let webdriver_url = format ! ( "{}:{}" , self . webdriver_url, self . webdriver_port, ) ;
943973
944- let client = ClientBuilder :: native ( )
945- . capabilities ( caps)
946- . connect ( & webdriver_url)
947- . await
948- . with_context ( || "WebDriver session errror" ) ?;
974+ // Reuse existing client or create new one
975+ let client = if let Some ( ref client) = self . webdriver_client {
976+ debug ! ( "Reusing existing WebDriver session" ) ;
977+ client. clone ( )
978+ } else {
979+ debug ! ( "Creating new WebDriver session" ) ;
980+ let new_client = ClientBuilder :: native ( )
981+ . capabilities ( caps)
982+ . connect ( & webdriver_url)
983+ . await
984+ . with_context ( || "WebDriver session error" ) ?;
985+ self . webdriver_client = Some ( new_client. clone ( ) ) ;
986+ new_client
987+ } ;
949988
950989 // URL-encode the HTML
951990 let data_uri = format ! ( "data:text/html,{}" , encode( data_uri) ) ;
@@ -978,7 +1017,8 @@ impl StaticExporter {
9781017
9791018 let data = client. execute_async ( js_script, args) . await ?;
9801019
981- client. close ( ) . await ?;
1020+ // Don't close the client - keep it for reuse
1021+ // client.close().await?;
9821022
9831023 let src = data. as_str ( ) . ok_or ( anyhow ! (
9841024 "Failed to execute Plotly.toImage in browser session"
0 commit comments