From 4c944e367f2bf3bc83968a06addcd78040a1b962 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:45:30 +0300 Subject: [PATCH 1/9] Build shared library --- rust/BUILD.bazel | 14 +++++++++++++- rust/defs.bzl | 2 ++ rust/private/rustfmt_wrapper.bzl | 5 +++++ rust/src/lib.rs | 12 ++++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/rust/BUILD.bazel b/rust/BUILD.bazel index 0a29911e1b9fc..e4912b0103d00 100644 --- a/rust/BUILD.bazel +++ b/rust/BUILD.bazel @@ -1,5 +1,5 @@ load("@crates//:defs.bzl", "all_crate_deps") -load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_test", "rustfmt_config") +load("//rust:defs.bzl", "rust_binary", "rust_library", "rust_shared_library", "rust_test", "rustfmt_config") rustfmt_config( name = "enable-rustfmt", @@ -96,6 +96,18 @@ rust_library( deps = all_crate_deps(normal = True), ) +rust_shared_library( + # The name here is used as the crate name + name = "selenium_manager_shared", + srcs = glob( + ["src/**/*.rs"], + exclude = ["main.rs"], + ), + edition = "2021", + visibility = ["//rust:__subpackages__"], + deps = all_crate_deps(normal = True), +) + filegroup( name = "selenium_manager_srcs", srcs = [ diff --git a/rust/defs.bzl b/rust/defs.bzl index bad75bf29de23..477a42d08a251 100644 --- a/rust/defs.bzl +++ b/rust/defs.bzl @@ -3,12 +3,14 @@ load( "//rust/private:rustfmt_wrapper.bzl", _rust_binary = "rust_binary", _rust_library = "rust_library", + _rust_shared_library = "rust_shared_library", _rust_test = "rust_test", _rust_test_suite = "rust_test_suite", ) rust_binary = _rust_binary rust_library = _rust_library +rust_shared_library = _rust_shared_library rust_test = _rust_test rust_test_suite = _rust_test_suite rustfmt_config = _rustfmt_config diff --git a/rust/private/rustfmt_wrapper.bzl b/rust/private/rustfmt_wrapper.bzl index fbaa3bde53c79..cfa2915731b62 100644 --- a/rust/private/rustfmt_wrapper.bzl +++ b/rust/private/rustfmt_wrapper.bzl @@ -4,6 +4,7 @@ load( "rustfmt_test", _rust_binary = "rust_binary", _rust_library = "rust_library", + _rust_shared_library = "rust_shared_library", _rust_test = "rust_test", _rust_test_suite = "rust_test_suite", ) @@ -27,6 +28,10 @@ def rust_library(name, **kwargs): _rust_library(name = name, **kwargs) _wrap_with_fmt_test(name, kwargs.get("tags", [])) +def rust_shared_library(name, **kwargs): + _rust_shared_library(name = name, **kwargs) + _wrap_with_fmt_test(name, kwargs.get("tags", [])) + def rust_binary(name, **kwargs): _rust_binary(name = name, **kwargs) _wrap_with_fmt_test(name, kwargs.get("tags", [])) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a2c2411bcb763..7ae81dcbda3a9 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1674,3 +1674,15 @@ fn get_index_version(full_version: &str, index: usize) -> Result .ok_or(anyhow!(format!("Wrong version: {}", full_version)))? .to_string()) } + +// ---------------------------------------------------------- +// Exported functions +// ---------------------------------------------------------- + +// this just an example how to expose function for external usage +#[no_mangle] +pub extern "C" fn get_test() -> *mut std::os::raw::c_char { + let a = get_manager_by_browser("chrome".to_string()).unwrap(); + let s = std::ffi::CString::new(a.get_driver_path_in_cache().unwrap().display().to_string()).unwrap(); + s.into_raw() // Transfer ownership to C +} \ No newline at end of file From 69e54054f0315f068aacda347291fb249494fb30 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Sun, 2 Mar 2025 20:39:16 +0300 Subject: [PATCH 2/9] Define cargo targets --- rust/Cargo.Bazel.lock | 2 +- rust/Cargo.toml | 7 +++++++ rust/src/lib.rs | 7 ++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/rust/Cargo.Bazel.lock b/rust/Cargo.Bazel.lock index f836f33c9d56a..dfa7a31e57523 100644 --- a/rust/Cargo.Bazel.lock +++ b/rust/Cargo.Bazel.lock @@ -1,5 +1,5 @@ { - "checksum": "e68bc8d92875cfa0a3769e6e31b05c99d2669f20fda466dd415ef9dcbd361b7d", + "checksum": "04c2f1c8b7c0ac539b226b5da83b0b41311961e3c8b2fed1a3dfae76721fec9c", "crates": { "addr2line 0.21.0": { "name": "addr2line", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 90df54f36e359..196ea74dd8a15 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -11,6 +11,13 @@ description = """ Selenium Manager is a CLI tool that automatically manages the browser/driver infrastructure required by Selenium. """ +[[bin]] +name = "selenium-manager" + +[lib] +name = "selenium_manager" +crate-type = ["cdylib", "rlib"] + [dependencies] clap = { version = "4.5.23", features = ["derive", "cargo"] } log = "0.4.22" diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7ae81dcbda3a9..58d0d231c1643 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1682,7 +1682,8 @@ fn get_index_version(full_version: &str, index: usize) -> Result // this just an example how to expose function for external usage #[no_mangle] pub extern "C" fn get_test() -> *mut std::os::raw::c_char { - let a = get_manager_by_browser("chrome".to_string()).unwrap(); - let s = std::ffi::CString::new(a.get_driver_path_in_cache().unwrap().display().to_string()).unwrap(); - s.into_raw() // Transfer ownership to C + let sm = get_manager_by_browser("chrome".to_string()).unwrap(); + + let dp = std::ffi::CString::new(sm.get_driver_path_in_cache().unwrap().display().to_string()).unwrap(); + dp.into_raw() // Transfer ownership to C } \ No newline at end of file From 775ab44113a77169bcb345a7008708c598de2082 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Thu, 6 Mar 2025 22:02:37 +0300 Subject: [PATCH 3/9] Added example of Log Callback --- rust/src/lib.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 58d0d231c1643..0765136529168 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1679,11 +1679,15 @@ fn get_index_version(full_version: &str, index: usize) -> Result // Exported functions // ---------------------------------------------------------- -// this just an example how to expose function for external usage +// this is callback function to be called each time when rust wants to send log data +type LogCallback = extern "C" fn(level: std::ffi::c_int, message: *const std::os::raw::c_char); + +// this is just an example how to expose function for external usage #[no_mangle] -pub extern "C" fn get_test() -> *mut std::os::raw::c_char { - let sm = get_manager_by_browser("chrome".to_string()).unwrap(); +pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log: LogCallback) -> *mut std::ffi::c_char { + for i in 1..6 { + log(i, std::ffi::CString::new("Hello, I am logging message").unwrap().as_ptr()); + } - let dp = std::ffi::CString::new(sm.get_driver_path_in_cache().unwrap().display().to_string()).unwrap(); - dp.into_raw() // Transfer ownership to C + return std::ffi::CString::new("This is dummy driver path").unwrap().into_raw(); } \ No newline at end of file From 15559d04b27ff5951fa943923a1022196d41d3bb Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 7 Mar 2025 17:26:46 +0300 Subject: [PATCH 4/9] Example how to free fn result --- rust/src/lib.rs | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 0765136529168..933f8fce50eec 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1682,12 +1682,45 @@ fn get_index_version(full_version: &str, index: usize) -> Result // this is callback function to be called each time when rust wants to send log data type LogCallback = extern "C" fn(level: std::ffi::c_int, message: *const std::os::raw::c_char); +#[repr(C)] +pub struct WebDriverPathResult { + success: bool, + driver_path: *mut std::ffi::c_char, + error: *mut std::ffi::c_char, +} + // this is just an example how to expose function for external usage #[no_mangle] -pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log: LogCallback) -> *mut std::ffi::c_char { +pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log: LogCallback) -> WebDriverPathResult { for i in 1..6 { - log(i, std::ffi::CString::new("Hello, I am logging message").unwrap().as_ptr()); + //let message = std::ffi::CString::new("Hello, I am logging message").unwrap(); + //log(i, message.as_ptr()); + } + + let driver = unsafe { std::ffi::CStr::from_ptr(driver_name).to_str().unwrap() }; + + return WebDriverPathResult { + success: true, + driver_path: std::ffi::CString::new("This is dummy driver path for ".to_owned() + driver).unwrap().into_raw(), + //driver_path: std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap().into_raw(), + error: std::ptr::null_mut(), } +} - return std::ffi::CString::new("This is dummy driver path").unwrap().into_raw(); -} \ No newline at end of file +#[no_mangle] +pub extern "C" fn free_webdriver_path_result(result: *mut WebDriverPathResult) { + if result.is_null() { + return; + } + unsafe { + let ffi_result = &mut *result; + if !ffi_result.driver_path.is_null() { + // Reconstruct CString to drop it and free memory + let _ = std::ffi::CString::from_raw(ffi_result.driver_path); + } + if !ffi_result.error.is_null() { + // Reconstruct CString to drop it and free memory + let _ = std::ffi::CString::from_raw(ffi_result.error); + } + } +} From eb287c150aa185c73426c239f18a023d6d3d006f Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 7 Mar 2025 19:47:12 +0300 Subject: [PATCH 5/9] Verify memory leakage for log events --- rust/src/lib.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 933f8fce50eec..4b5ae3dfdb36e 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1693,8 +1693,9 @@ pub struct WebDriverPathResult { #[no_mangle] pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log: LogCallback) -> WebDriverPathResult { for i in 1..6 { - //let message = std::ffi::CString::new("Hello, I am logging message").unwrap(); - //log(i, message.as_ptr()); + let message = std::ffi::CString::new("Hello, I am logging message").unwrap(); + //let message = std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap(); + log(i, message.as_ptr()); } let driver = unsafe { std::ffi::CStr::from_ptr(driver_name).to_str().unwrap() }; From 1205f5288c6d7c40fa35a367f4aa8fa5929024a8 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 7 Mar 2025 22:14:07 +0300 Subject: [PATCH 6/9] Handle panic --- rust/src/lib.rs | 50 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4b5ae3dfdb36e..4caf4c92e855b 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1692,19 +1692,31 @@ pub struct WebDriverPathResult { // this is just an example how to expose function for external usage #[no_mangle] pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log: LogCallback) -> WebDriverPathResult { - for i in 1..6 { - let message = std::ffi::CString::new("Hello, I am logging message").unwrap(); - //let message = std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap(); - log(i, message.as_ptr()); - } - - let driver = unsafe { std::ffi::CStr::from_ptr(driver_name).to_str().unwrap() }; - - return WebDriverPathResult { - success: true, - driver_path: std::ffi::CString::new("This is dummy driver path for ".to_owned() + driver).unwrap().into_raw(), - //driver_path: std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap().into_raw(), - error: std::ptr::null_mut(), + let result = std::panic::catch_unwind(|| { + + for i in 1..6 { + let message = std::ffi::CString::new("Hello, I am logging message").unwrap(); + //let message = std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap(); + log(i, message.as_ptr()); + } + + let driver = unsafe { std::ffi::CStr::from_ptr(driver_name).to_str().unwrap() }; + + return std::ffi::CString::new("This is dummy driver path for ".to_owned() + driver).unwrap().into_raw(); + //return std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap().into_raw(); + }); + + match result { + Ok(driver_path) => WebDriverPathResult { + success: true, + driver_path, + error: std::ptr::null_mut(), + }, + Err(panic) => WebDriverPathResult { + success: false, + driver_path: std::ptr::null_mut(), + error: std::ffi::CString::new(extract_panic_message(panic)).unwrap().into_raw(), + } } } @@ -1725,3 +1737,15 @@ pub extern "C" fn free_webdriver_path_result(result: *mut WebDriverPathResult) { } } } + +/// Extract panic message from `Box` +fn extract_panic_message(panic: Box) -> String { + // Try to downcast to common panic types + if let Some(s) = panic.downcast_ref::() { + s.clone() + } else if let Some(s) = panic.downcast_ref::<&str>() { + s.to_string() + } else { + "Unknown panic (non-string payload)".to_string() + } +} From 5c06ba7a5dc5dc61634c41f46154eb426a4220c3 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 7 Mar 2025 23:10:15 +0300 Subject: [PATCH 7/9] Intentional panic --- rust/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 4caf4c92e855b..7df91941b4b40 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1700,6 +1700,8 @@ pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log(i, message.as_ptr()); } + //panic!("Intentional panic for testing"); + let driver = unsafe { std::ffi::CStr::from_ptr(driver_name).to_str().unwrap() }; return std::ffi::CString::new("This is dummy driver path for ".to_owned() + driver).unwrap().into_raw(); From 6d4be86af5ceb723d18a4de46ea9dd0f809da2a2 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Fri, 7 Mar 2025 23:52:03 +0300 Subject: [PATCH 8/9] Move extern function to ffi.rs --- rust/src/ffi.rs | 91 +++++++++++++++++++++++++++++++++++++++++++++++++ rust/src/lib.rs | 84 ++++----------------------------------------- 2 files changed, 98 insertions(+), 77 deletions(-) create mode 100644 rust/src/ffi.rs diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs new file mode 100644 index 0000000000000..ba521d2bba145 --- /dev/null +++ b/rust/src/ffi.rs @@ -0,0 +1,91 @@ +// Licensed to the Software Freedom Conservancy (SFC) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The SFC licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use std::{ffi::{c_char, c_int, CStr, CString}, ptr::null_mut}; + +// this is callback function to be called each time when rust wants to send log data +type LogCallback = extern "C" fn(level: c_int, message: *const std::os::raw::c_char); + +#[repr(C)] +pub struct WebDriverPathResult { + success: bool, + driver_path: *mut c_char, + error: *mut c_char, +} + +// this is just an example how to expose function for external usage +#[no_mangle] +pub extern "C" fn get_dummy_webdriver_path(driver_name: *const c_char, log: LogCallback) -> WebDriverPathResult { + let result = std::panic::catch_unwind(|| { + + for i in 1..6 { + let message = CString::new("Hello, I am logging message").unwrap(); + //let message = CString::new(String::from("A").repeat(10_000_000)).unwrap(); + log(i, message.as_ptr()); + } + + //panic!("Intentional panic for testing"); + + let driver = unsafe { CStr::from_ptr(driver_name).to_str().unwrap() }; + + return CString::new("This is dummy driver path for ".to_owned() + driver).unwrap().into_raw(); + //return CString::new(String::from("A").repeat(10_000_000)).unwrap().into_raw(); + }); + + match result { + Ok(driver_path) => WebDriverPathResult { + success: true, + driver_path, + error: null_mut(), + }, + Err(panic) => WebDriverPathResult { + success: false, + driver_path: null_mut(), + error: CString::new(extract_panic_message(panic)).unwrap().into_raw(), + } + } +} + +#[no_mangle] +pub extern "C" fn free_webdriver_path_result(result: *mut WebDriverPathResult) { + if result.is_null() { + return; + } + unsafe { + let ffi_result = &mut *result; + if !ffi_result.driver_path.is_null() { + // Reconstruct CString to drop it and free memory + let _ = CString::from_raw(ffi_result.driver_path); + } + if !ffi_result.error.is_null() { + // Reconstruct CString to drop it and free memory + let _ = CString::from_raw(ffi_result.error); + } + } +} + +/// Extract panic message from `Box` +fn extract_panic_message(panic: Box) -> String { + // Try to downcast to common panic types + if let Some(s) = panic.downcast_ref::() { + s.clone() + } else if let Some(s) = panic.downcast_ref::<&str>() { + s.to_string() + } else { + "Unknown panic (non-string payload)".to_string() + } +} \ No newline at end of file diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 7df91941b4b40..2565624510396 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -42,6 +42,12 @@ use crate::shell::{ use crate::stats::{send_stats_to_plausible, Props}; use anyhow::anyhow; use anyhow::Error; +// Re-export FFI functionality +pub use ffi::{ + WebDriverPathResult, + get_dummy_webdriver_path, + free_webdriver_path_result +}; use reqwest::{Client, Proxy}; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -57,6 +63,7 @@ pub mod downloads; pub mod edge; pub mod files; pub mod firefox; +pub mod ffi; pub mod grid; pub mod iexplorer; pub mod lock; @@ -1674,80 +1681,3 @@ fn get_index_version(full_version: &str, index: usize) -> Result .ok_or(anyhow!(format!("Wrong version: {}", full_version)))? .to_string()) } - -// ---------------------------------------------------------- -// Exported functions -// ---------------------------------------------------------- - -// this is callback function to be called each time when rust wants to send log data -type LogCallback = extern "C" fn(level: std::ffi::c_int, message: *const std::os::raw::c_char); - -#[repr(C)] -pub struct WebDriverPathResult { - success: bool, - driver_path: *mut std::ffi::c_char, - error: *mut std::ffi::c_char, -} - -// this is just an example how to expose function for external usage -#[no_mangle] -pub extern "C" fn get_dummy_webdriver_path(driver_name: *const std::ffi::c_char, log: LogCallback) -> WebDriverPathResult { - let result = std::panic::catch_unwind(|| { - - for i in 1..6 { - let message = std::ffi::CString::new("Hello, I am logging message").unwrap(); - //let message = std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap(); - log(i, message.as_ptr()); - } - - //panic!("Intentional panic for testing"); - - let driver = unsafe { std::ffi::CStr::from_ptr(driver_name).to_str().unwrap() }; - - return std::ffi::CString::new("This is dummy driver path for ".to_owned() + driver).unwrap().into_raw(); - //return std::ffi::CString::new(String::from("A").repeat(10_000_000)).unwrap().into_raw(); - }); - - match result { - Ok(driver_path) => WebDriverPathResult { - success: true, - driver_path, - error: std::ptr::null_mut(), - }, - Err(panic) => WebDriverPathResult { - success: false, - driver_path: std::ptr::null_mut(), - error: std::ffi::CString::new(extract_panic_message(panic)).unwrap().into_raw(), - } - } -} - -#[no_mangle] -pub extern "C" fn free_webdriver_path_result(result: *mut WebDriverPathResult) { - if result.is_null() { - return; - } - unsafe { - let ffi_result = &mut *result; - if !ffi_result.driver_path.is_null() { - // Reconstruct CString to drop it and free memory - let _ = std::ffi::CString::from_raw(ffi_result.driver_path); - } - if !ffi_result.error.is_null() { - // Reconstruct CString to drop it and free memory - let _ = std::ffi::CString::from_raw(ffi_result.error); - } - } -} - -/// Extract panic message from `Box` -fn extract_panic_message(panic: Box) -> String { - // Try to downcast to common panic types - if let Some(s) = panic.downcast_ref::() { - s.clone() - } else if let Some(s) = panic.downcast_ref::<&str>() { - s.to_string() - } else { - "Unknown panic (non-string payload)".to_string() - } -} From 0bc4ab425017b0aa0aec0fb7e774dac4888acef7 Mon Sep 17 00:00:00 2001 From: Nikolay Borisenko <22616990+nvborisenko@users.noreply.github.com> Date: Wed, 12 Mar 2025 11:38:43 +0300 Subject: [PATCH 9/9] Simplify exporting of ffi module --- rust/src/lib.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 2565624510396..2308dccb55673 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -42,12 +42,6 @@ use crate::shell::{ use crate::stats::{send_stats_to_plausible, Props}; use anyhow::anyhow; use anyhow::Error; -// Re-export FFI functionality -pub use ffi::{ - WebDriverPathResult, - get_dummy_webdriver_path, - free_webdriver_path_result -}; use reqwest::{Client, Proxy}; use std::collections::HashMap; use std::path::{Path, PathBuf}; @@ -63,7 +57,6 @@ pub mod downloads; pub mod edge; pub mod files; pub mod firefox; -pub mod ffi; pub mod grid; pub mod iexplorer; pub mod lock; @@ -75,6 +68,8 @@ pub mod safaritp; pub mod shell; pub mod stats; +mod ffi; + pub const REQUEST_TIMEOUT_SEC: u64 = 300; // The timeout is applied from when the request starts connecting until the response body has finished pub const STABLE: &str = "stable"; pub const BETA: &str = "beta";