├── src ├── imp │ ├── .gitignore │ ├── mod.rs │ ├── request_handler.rs │ ├── print_handler.rs │ ├── print_pdf_callback.rs │ ├── life_span_handler.rs │ ├── browser_view_delegate.rs │ ├── context_menu_handler.rs │ ├── browser_process_handler.rs │ ├── app.rs │ ├── run_file_dialog_callback.rs │ ├── display_handler.rs │ ├── browser.rs │ ├── render_process_handler.rs │ ├── window_delegate.rs │ ├── v8_pdf_print_handler.rs │ ├── v8_file_dialog_handler.rs │ └── client.rs └── lib.rs ├── .gitignore ├── icon.png ├── .gitattributes ├── cefwrapper.h ├── examples ├── osk │ ├── page.html │ └── main.rs ├── fullscreen │ ├── page.html │ └── main.rs ├── simple.rs ├── mithril │ ├── main.rs │ └── index.html ├── printing │ ├── main.rs │ └── page.html └── file-dialogs │ ├── main.rs │ └── page.html ├── Cargo.toml ├── LICENSE-MIT ├── README.md ├── .vscode └── launch.json └── LICENSE-APACHE /src/imp/.gitignore: -------------------------------------------------------------------------------- 1 | bindings.rs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | /GPUCache 5 | *.lib -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamaluik/cef-simple/master/icon.png -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.so filter=lfs diff=lfs merge=lfs -text 2 | *.bin filter=lfs diff=lfs merge=lfs -text 3 | *.pak filter=lfs diff=lfs merge=lfs -text 4 | *.dat filter=lfs diff=lfs merge=lfs -text 5 | *.lib filter=lfs diff=lfs merge=lfs -text 6 | *.dll filter=lfs diff=lfs merge=lfs -text 7 | -------------------------------------------------------------------------------- /cefwrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | -------------------------------------------------------------------------------- /examples/osk/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CEF File Dialogs Demo 7 | 8 | 9 |
10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/imp/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod bindings; 3 | pub mod browser; 4 | pub mod browser_process_handler; 5 | pub mod browser_view_delegate; 6 | pub mod client; 7 | pub mod context_menu_handler; 8 | pub mod display_handler; 9 | pub mod life_span_handler; 10 | pub mod print_handler; 11 | pub mod print_pdf_callback; 12 | pub mod render_process_handler; 13 | pub mod request_handler; 14 | pub mod run_file_dialog_callback; 15 | pub mod v8_file_dialog_handler; 16 | pub mod v8_pdf_print_handler; 17 | pub mod window_delegate; 18 | -------------------------------------------------------------------------------- /examples/fullscreen/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CEF Fullscreen Demo 7 | 8 | 9 | 15 | Click here to go fullscreen. 16 | 17 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cef-simple" 3 | version = "0.3.7" 4 | authors = ["Kenton Hamaluik "] 5 | edition = "2018" 6 | build = "build.rs" 7 | description = "Small library / bindings for CEF in Rust" 8 | repository = "https://github.com/hamaluik/cef-simple" 9 | readme = "README.md" 10 | keywords = ["cef", "chromium"] 11 | categories = ["api-bindings", "external-ffi-bindings"] 12 | license = "MIT/Apache-2.0" 13 | 14 | [dependencies] 15 | log = "0.4" 16 | 17 | [build-dependencies] 18 | bindgen = "0.58" 19 | 20 | [target.'cfg(windows)'.dependencies] 21 | winapi = { version = "0.3", features = ["libloaderapi"] } 22 | 23 | [dev-dependencies] 24 | simplelog = "0.8" 25 | urlencoding = "1.1" 26 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | use cef_simple::{Cef, WindowOptions}; 2 | 3 | fn main() -> Result<(), Box> { 4 | let cef = Cef::initialize(None, true)?; 5 | 6 | cef.open_window(WindowOptions { 7 | url: "https://www.rust-lang.org/".to_owned(), 8 | title: Some("CEF Simple".to_string()), 9 | window_icon: Some(include_bytes!(concat!( 10 | env!("CARGO_MANIFEST_DIR"), 11 | "/icon.png" 12 | ))), 13 | window_app_icon: Some(include_bytes!(concat!( 14 | env!("CARGO_MANIFEST_DIR"), 15 | "/icon.png" 16 | ))), 17 | ..WindowOptions::default() 18 | })?; 19 | 20 | cef.run()?; 21 | 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /examples/mithril/main.rs: -------------------------------------------------------------------------------- 1 | use cef_simple::{Cef, WindowOptions}; 2 | use simplelog::*; 3 | 4 | fn main() -> Result<(), Box> { 5 | CombinedLogger::init(vec![TermLogger::new( 6 | LevelFilter::Trace, 7 | Config::default(), 8 | TerminalMode::Mixed, 9 | )]) 10 | .unwrap(); 11 | 12 | let cef = Cef::initialize(Some(8000), false)?; 13 | 14 | let page = urlencoding::encode(include_str!("index.html")); 15 | 16 | cef.open_window(WindowOptions { 17 | url: format!("data:text/html,{}", page), 18 | title: Some("CEF Mithril Demo".to_string()), 19 | window_icon: Some(include_bytes!(concat!( 20 | env!("CARGO_MANIFEST_DIR"), 21 | "/icon.png" 22 | ))), 23 | window_app_icon: Some(include_bytes!(concat!( 24 | env!("CARGO_MANIFEST_DIR"), 25 | "/icon.png" 26 | ))), 27 | ..WindowOptions::default() 28 | })?; 29 | 30 | cef.run()?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/printing/main.rs: -------------------------------------------------------------------------------- 1 | use cef_simple::{Cef, WindowOptions}; 2 | use simplelog::*; 3 | 4 | fn main() -> Result<(), Box> { 5 | let cef = Cef::initialize(None, true)?; 6 | 7 | CombinedLogger::init(vec![TermLogger::new( 8 | LevelFilter::Trace, 9 | Config::default(), 10 | TerminalMode::Mixed, 11 | )]) 12 | .unwrap(); 13 | 14 | let page = urlencoding::encode(include_str!("page.html")); 15 | 16 | cef.open_window(WindowOptions { 17 | url: format!("data:text/html,{}", page), 18 | title: Some("CEF Simple—Printing Demo".to_string()), 19 | window_icon: Some(include_bytes!(concat!( 20 | env!("CARGO_MANIFEST_DIR"), 21 | "/icon.png" 22 | ))), 23 | window_app_icon: Some(include_bytes!(concat!( 24 | env!("CARGO_MANIFEST_DIR"), 25 | "/icon.png" 26 | ))), 27 | ..WindowOptions::default() 28 | })?; 29 | 30 | cef.run()?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/fullscreen/main.rs: -------------------------------------------------------------------------------- 1 | use cef_simple::{Cef, WindowOptions}; 2 | use simplelog::*; 3 | 4 | fn main() -> Result<(), Box> { 5 | let cef = Cef::initialize(None, true)?; 6 | 7 | CombinedLogger::init(vec![TermLogger::new( 8 | LevelFilter::Trace, 9 | Config::default(), 10 | TerminalMode::Mixed, 11 | )]) 12 | .unwrap(); 13 | 14 | let page = urlencoding::encode(include_str!("page.html")); 15 | 16 | cef.open_window(WindowOptions { 17 | url: format!("data:text/html,{}", page), 18 | title: Some("CEF Simple—Fullscreen Demo".to_string()), 19 | window_icon: Some(include_bytes!(concat!( 20 | env!("CARGO_MANIFEST_DIR"), 21 | "/icon.png" 22 | ))), 23 | window_app_icon: Some(include_bytes!(concat!( 24 | env!("CARGO_MANIFEST_DIR"), 25 | "/icon.png" 26 | ))), 27 | ..WindowOptions::default() 28 | })?; 29 | 30 | cef.run()?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/osk/main.rs: -------------------------------------------------------------------------------- 1 | use cef_simple::{Cef, WindowOptions}; 2 | use simplelog::*; 3 | 4 | fn main() -> Result<(), Box> { 5 | let cef = Cef::initialize(Some(8000), false)?; 6 | 7 | CombinedLogger::init(vec![TermLogger::new( 8 | LevelFilter::Trace, 9 | Config::default(), 10 | TerminalMode::Mixed, 11 | )]) 12 | .unwrap(); 13 | 14 | let page = urlencoding::encode(include_str!("page.html")); 15 | 16 | cef.open_window(WindowOptions { 17 | url: format!("data:text/html,{}", page), 18 | title: Some("CEF Simple—On-Screen-Keyboard Demo".to_string()), 19 | window_icon: Some(include_bytes!(concat!( 20 | env!("CARGO_MANIFEST_DIR"), 21 | "/icon.png" 22 | ))), 23 | window_app_icon: Some(include_bytes!(concat!( 24 | env!("CARGO_MANIFEST_DIR"), 25 | "/icon.png" 26 | ))), 27 | ..WindowOptions::default() 28 | })?; 29 | 30 | cef.run()?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /examples/file-dialogs/main.rs: -------------------------------------------------------------------------------- 1 | use cef_simple::{Cef, WindowOptions}; 2 | use simplelog::*; 3 | 4 | fn main() -> Result<(), Box> { 5 | let cef = Cef::initialize(Some(8000), false)?; 6 | 7 | CombinedLogger::init(vec![TermLogger::new( 8 | LevelFilter::Trace, 9 | Config::default(), 10 | TerminalMode::Mixed, 11 | )]) 12 | .unwrap(); 13 | 14 | let page = urlencoding::encode(include_str!("page.html")); 15 | 16 | cef.open_window(WindowOptions { 17 | url: format!("data:text/html,{}", page), 18 | title: Some("CEF Simple—File Dialogs Demo".to_string()), 19 | window_icon: Some(include_bytes!(concat!( 20 | env!("CARGO_MANIFEST_DIR"), 21 | "/icon.png" 22 | ))), 23 | window_app_icon: Some(include_bytes!(concat!( 24 | env!("CARGO_MANIFEST_DIR"), 25 | "/icon.png" 26 | ))), 27 | ..WindowOptions::default() 28 | })?; 29 | 30 | cef.run()?; 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 Kenton Hamaluik 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cef-simple 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/tilecoding.svg)](https://crates.io/crates/cef-simple) 4 | [![Docs](https://docs.rs/cef-simple/badge.svg)](https://docs.rs/crate/cef-simple/) 5 | 6 | These are simple bindings for [CEF](https://bitbucket.org/chromiumembedded/cef/src/master/) `C API` in [Rust](https://www.rust-lang.org/). These bindings are far from complete and are currently geared towards my own uses. The library follows the "CEFSimple" examples from CEF, and so doesn't work on mac. 7 | 8 | It's also not documented yet, but you can follow the [example](examples/simple.rs) to figure out how to use. It's pretty simple. 9 | 10 | I haven't sorted out how best to include the CEF distribution, so for now you have to provide an environment variable `CEF_PATH` which points to the CEF distribution folder (the one that contains the `Release` and `Resources` folders). 11 | 12 | In order to run the examples, the CEF supporting files must be placed beside the executable. That means, from the CEF directory, copy the contents of the `Release` and `Resources` folders into `target/debug/` or `target/release/` as necessary, and make sure these files are included in any binary distributions. Making this more ergonomic is also on the TODO list. 13 | -------------------------------------------------------------------------------- /examples/file-dialogs/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CEF File Dialogs Demo 7 | 36 | 37 | 38 | 39 |   40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/printing/page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CEF Print Demo 7 | 30 | 47 | 48 | 49 | 51 |

52 | Click here to print to PDF, or here to print normally. 53 |

54 |

55 | 56 | -------------------------------------------------------------------------------- /src/imp/request_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{cef_base_ref_counted_t, cef_request_handler_t}; 6 | 7 | #[derive(Debug)] 8 | #[repr(C)] 9 | pub struct RequestHandler { 10 | request_handler: cef_request_handler_t, 11 | ref_count: AtomicUsize, 12 | } 13 | 14 | impl RequestHandler { 15 | pub fn inc_ref(&self) { 16 | self.ref_count.fetch_add(1, Ordering::SeqCst); 17 | } 18 | } 19 | 20 | pub fn allocate() -> *mut RequestHandler { 21 | let handler = RequestHandler { 22 | request_handler: cef_request_handler_t { 23 | base: cef_base_ref_counted_t { 24 | size: size_of::() as u64, 25 | add_ref: Some(add_ref), 26 | release: Some(release), 27 | has_one_ref: Some(has_one_ref), 28 | has_at_least_one_ref: Some(has_at_least_one_ref), 29 | }, 30 | on_before_browse: None, 31 | on_open_urlfrom_tab: None, 32 | get_resource_request_handler: None, 33 | get_auth_credentials: None, 34 | on_quota_request: None, 35 | on_certificate_error: None, 36 | on_select_client_certificate: None, 37 | on_plugin_crashed: None, 38 | on_render_view_ready: None, 39 | on_render_process_terminated: None, 40 | }, 41 | ref_count: AtomicUsize::new(1), 42 | }; 43 | 44 | Box::into_raw(Box::from(handler)) 45 | } 46 | 47 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 48 | let request_handler = base as *mut RequestHandler; 49 | unsafe { 50 | (*request_handler).ref_count.fetch_add(1, Ordering::SeqCst); 51 | } 52 | } 53 | 54 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 55 | let request_handler = base as *mut RequestHandler; 56 | let count = unsafe { (*request_handler).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 57 | 58 | if count == 0 { 59 | unsafe { 60 | Box::from_raw(request_handler); 61 | } 62 | 1 63 | } else { 64 | 0 65 | } 66 | } 67 | 68 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 69 | let request_handler = base as *mut RequestHandler; 70 | let count = unsafe { (*request_handler).ref_count.load(Ordering::SeqCst) }; 71 | if count == 1 { 72 | 1 73 | } else { 74 | 0 75 | } 76 | } 77 | 78 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 79 | let request_handler = base as *mut RequestHandler; 80 | let count = unsafe { (*request_handler).ref_count.load(Ordering::SeqCst) }; 81 | if count >= 1 { 82 | 1 83 | } else { 84 | 0 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/imp/print_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{cef_base_ref_counted_t, cef_print_handler_t, cef_size_t}; 6 | 7 | #[derive(Debug)] 8 | #[repr(C)] 9 | pub struct PrintHandler { 10 | print_handler: cef_print_handler_t, 11 | ref_count: AtomicUsize, 12 | } 13 | 14 | unsafe extern "C" fn get_pdf_paper_size( 15 | _slf: *mut cef_print_handler_t, 16 | device_units_per_inch: c_int, 17 | ) -> cef_size_t { 18 | let mm_per_in = 25.4; 19 | // TODO: less hacky / hard-coded? 20 | let device_units_per_mm = (device_units_per_inch as f64) / mm_per_in; 21 | let width = 210.0 * device_units_per_mm; 22 | let height = 297.0 * device_units_per_mm; 23 | cef_size_t { 24 | width: width as i32, 25 | height: height as i32, 26 | } 27 | } 28 | 29 | pub fn allocate() -> *mut PrintHandler { 30 | let handler = PrintHandler { 31 | print_handler: cef_print_handler_t { 32 | base: cef_base_ref_counted_t { 33 | size: size_of::() as u64, 34 | add_ref: Some(add_ref), 35 | release: Some(release), 36 | has_one_ref: Some(has_one_ref), 37 | has_at_least_one_ref: Some(has_at_least_one_ref), 38 | }, 39 | // TODO: implement these? 40 | on_print_start: None, 41 | on_print_settings: None, 42 | on_print_dialog: None, 43 | on_print_job: None, 44 | on_print_reset: None, 45 | get_pdf_paper_size: Some(get_pdf_paper_size), 46 | }, 47 | ref_count: AtomicUsize::new(1), 48 | }; 49 | 50 | Box::into_raw(Box::from(handler)) 51 | } 52 | 53 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 54 | let print_handler = base as *mut PrintHandler; 55 | unsafe { 56 | (*print_handler).ref_count.fetch_add(1, Ordering::SeqCst); 57 | } 58 | } 59 | 60 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 61 | let print_handler = base as *mut PrintHandler; 62 | let count = unsafe { (*print_handler).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 63 | 64 | if count == 0 { 65 | unsafe { 66 | Box::from_raw(print_handler); 67 | } 68 | 1 69 | } else { 70 | 0 71 | } 72 | } 73 | 74 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 75 | let print_handler = base as *mut PrintHandler; 76 | let count = unsafe { (*print_handler).ref_count.load(Ordering::SeqCst) }; 77 | if count == 1 { 78 | 1 79 | } else { 80 | 0 81 | } 82 | } 83 | 84 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 85 | let print_handler = base as *mut PrintHandler; 86 | let count = unsafe { (*print_handler).ref_count.load(Ordering::SeqCst) }; 87 | if count >= 1 { 88 | 1 89 | } else { 90 | 0 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/imp/print_pdf_callback.rs: -------------------------------------------------------------------------------- 1 | use super::bindings::{_cef_pdf_print_callback_t, cef_base_ref_counted_t, cef_string_t}; 2 | use std::mem::size_of; 3 | use std::os::raw::c_int; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | 6 | #[repr(C)] 7 | pub struct PDFPrintCallback { 8 | pdf_print_callback: _cef_pdf_print_callback_t, 9 | ref_count: AtomicUsize, 10 | on_done: Option>, 11 | } 12 | 13 | unsafe extern "C" fn on_pdf_print_finished( 14 | slf: *mut _cef_pdf_print_callback_t, 15 | _path: *const cef_string_t, 16 | ok: c_int, 17 | ) { 18 | let callback = slf as *mut PDFPrintCallback; 19 | if let Some(on_done) = &mut (*callback).on_done { 20 | on_done(ok == 1); 21 | } 22 | } 23 | 24 | pub fn allocate(on_done: Option>) -> *mut PDFPrintCallback { 25 | let handler = PDFPrintCallback { 26 | pdf_print_callback: _cef_pdf_print_callback_t { 27 | base: cef_base_ref_counted_t { 28 | size: size_of::() as u64, 29 | add_ref: Some(add_ref_pdf_print_callback), 30 | release: Some(release_pdf_print_callback), 31 | has_one_ref: Some(has_one_ref_pdf_print_callback), 32 | has_at_least_one_ref: Some(has_at_least_one_ref_pdf_print_callback), 33 | }, 34 | on_pdf_print_finished: Some(on_pdf_print_finished), 35 | }, 36 | ref_count: AtomicUsize::new(1), 37 | on_done, 38 | }; 39 | 40 | Box::into_raw(Box::from(handler)) 41 | } 42 | 43 | extern "C" fn add_ref_pdf_print_callback(base: *mut cef_base_ref_counted_t) { 44 | let life_span_handler = base as *mut PDFPrintCallback; 45 | unsafe { 46 | (*life_span_handler) 47 | .ref_count 48 | .fetch_add(1, Ordering::SeqCst); 49 | } 50 | } 51 | 52 | extern "C" fn release_pdf_print_callback(base: *mut cef_base_ref_counted_t) -> c_int { 53 | let life_span_handler = base as *mut PDFPrintCallback; 54 | let count = unsafe { 55 | (*life_span_handler) 56 | .ref_count 57 | .fetch_sub(1, Ordering::SeqCst) 58 | - 1 59 | }; 60 | 61 | if count == 0 { 62 | unsafe { 63 | Box::from_raw(life_span_handler); 64 | } 65 | 1 66 | } else { 67 | 0 68 | } 69 | } 70 | 71 | extern "C" fn has_one_ref_pdf_print_callback(base: *mut cef_base_ref_counted_t) -> c_int { 72 | let life_span_handler = base as *mut PDFPrintCallback; 73 | let count = unsafe { (*life_span_handler).ref_count.load(Ordering::SeqCst) }; 74 | if count == 1 { 75 | 1 76 | } else { 77 | 0 78 | } 79 | } 80 | 81 | extern "C" fn has_at_least_one_ref_pdf_print_callback(base: *mut cef_base_ref_counted_t) -> c_int { 82 | let life_span_handler = base as *mut PDFPrintCallback; 83 | let count = unsafe { (*life_span_handler).ref_count.load(Ordering::SeqCst) }; 84 | if count >= 1 { 85 | 1 86 | } else { 87 | 0 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/imp/life_span_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_life_span_handler_t, cef_quit_message_loop, 7 | }; 8 | 9 | #[repr(C)] 10 | pub struct LifeSpanHandler { 11 | life_span_handler: cef_life_span_handler_t, 12 | ref_count: AtomicUsize, 13 | } 14 | 15 | impl LifeSpanHandler { 16 | pub fn inc_ref(&self) { 17 | self.ref_count.fetch_add(1, Ordering::SeqCst); 18 | } 19 | } 20 | 21 | extern "C" fn do_close(_slf: *mut cef_life_span_handler_t, _browser: *mut cef_browser_t) -> c_int { 22 | 0 23 | } 24 | 25 | unsafe extern "C" fn on_before_close( 26 | _slf: *mut cef_life_span_handler_t, 27 | _browser: *mut cef_browser_t, 28 | ) { 29 | cef_quit_message_loop(); 30 | } 31 | 32 | pub fn allocate() -> *mut LifeSpanHandler { 33 | let handler = LifeSpanHandler { 34 | life_span_handler: cef_life_span_handler_t { 35 | base: cef_base_ref_counted_t { 36 | size: size_of::() as u64, 37 | add_ref: Some(add_ref), 38 | release: Some(release), 39 | has_one_ref: Some(has_one_ref), 40 | has_at_least_one_ref: Some(has_at_least_one_ref), 41 | }, 42 | on_before_popup: None, 43 | on_after_created: None, 44 | do_close: Some(do_close), 45 | on_before_close: Some(on_before_close), 46 | }, 47 | ref_count: AtomicUsize::new(1), 48 | }; 49 | 50 | Box::into_raw(Box::from(handler)) 51 | } 52 | 53 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 54 | let life_span_handler = base as *mut LifeSpanHandler; 55 | unsafe { 56 | (*life_span_handler) 57 | .ref_count 58 | .fetch_add(1, Ordering::SeqCst); 59 | } 60 | } 61 | 62 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 63 | let life_span_handler = base as *mut LifeSpanHandler; 64 | let count = unsafe { 65 | (*life_span_handler) 66 | .ref_count 67 | .fetch_sub(1, Ordering::SeqCst) 68 | - 1 69 | }; 70 | 71 | if count == 0 { 72 | unsafe { 73 | Box::from_raw(life_span_handler); 74 | } 75 | 1 76 | } else { 77 | 0 78 | } 79 | } 80 | 81 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 82 | let life_span_handler = base as *mut LifeSpanHandler; 83 | let count = unsafe { (*life_span_handler).ref_count.load(Ordering::SeqCst) }; 84 | if count == 1 { 85 | 1 86 | } else { 87 | 0 88 | } 89 | } 90 | 91 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 92 | let life_span_handler = base as *mut LifeSpanHandler; 93 | let count = unsafe { (*life_span_handler).ref_count.load(Ordering::SeqCst) }; 94 | if count >= 1 { 95 | 1 96 | } else { 97 | 0 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /examples/mithril/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | CEF Mithril Demo 7 | 52 | 53 | 54 | 55 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /src/imp/browser_view_delegate.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{cef_base_ref_counted_t, cef_browser_view_delegate_t, cef_view_delegate_t}; 6 | 7 | #[derive(Debug)] 8 | #[repr(C)] 9 | pub struct BrowserViewDelegate { 10 | browser_view_delegate: cef_browser_view_delegate_t, 11 | ref_count: AtomicUsize, 12 | } 13 | 14 | impl BrowserViewDelegate { 15 | pub fn inc_ref(&self) { 16 | self.ref_count.fetch_add(1, Ordering::SeqCst); 17 | } 18 | } 19 | 20 | pub fn allocate() -> *mut BrowserViewDelegate { 21 | let browser_view = BrowserViewDelegate { 22 | browser_view_delegate: cef_browser_view_delegate_t { 23 | base: cef_view_delegate_t { 24 | base: cef_base_ref_counted_t { 25 | size: size_of::() as u64, 26 | add_ref: Some(add_ref), 27 | release: Some(release), 28 | has_one_ref: Some(has_one_ref), 29 | has_at_least_one_ref: Some(has_at_least_one_ref), 30 | }, 31 | get_preferred_size: None, 32 | get_minimum_size: None, 33 | get_maximum_size: None, 34 | get_height_for_width: None, 35 | on_parent_view_changed: None, 36 | on_child_view_changed: None, 37 | on_focus: None, 38 | on_blur: None, 39 | }, 40 | on_browser_created: None, 41 | on_browser_destroyed: None, 42 | get_delegate_for_popup_browser_view: None, 43 | on_popup_browser_view_created: None, 44 | }, 45 | ref_count: AtomicUsize::new(1), 46 | }; 47 | 48 | Box::into_raw(Box::from(browser_view)) 49 | } 50 | 51 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 52 | let browser_view_delegate = base as *mut BrowserViewDelegate; 53 | unsafe { 54 | (*browser_view_delegate) 55 | .ref_count 56 | .fetch_add(1, Ordering::SeqCst); 57 | } 58 | } 59 | 60 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 61 | let browser_view_delegate = base as *mut BrowserViewDelegate; 62 | let count = unsafe { 63 | (*browser_view_delegate) 64 | .ref_count 65 | .fetch_sub(1, Ordering::SeqCst) 66 | - 1 67 | }; 68 | 69 | if count == 0 { 70 | unsafe { 71 | Box::from_raw(browser_view_delegate as *mut BrowserViewDelegate); 72 | } 73 | 1 74 | } else { 75 | 0 76 | } 77 | } 78 | 79 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 80 | let browser_view_delegate = base as *mut BrowserViewDelegate; 81 | let count = unsafe { (*browser_view_delegate).ref_count.load(Ordering::SeqCst) }; 82 | if count == 1 { 83 | 1 84 | } else { 85 | 0 86 | } 87 | } 88 | 89 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 90 | let browser_view_delegate = base as *mut BrowserViewDelegate; 91 | let count = unsafe { (*browser_view_delegate).ref_count.load(Ordering::SeqCst) }; 92 | if count >= 1 { 93 | 1 94 | } else { 95 | 0 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/imp/context_menu_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_context_menu_handler_t, cef_context_menu_params_t, 7 | cef_frame_t, cef_menu_model_t, 8 | }; 9 | 10 | #[repr(C)] 11 | pub struct ContextMenuHandler { 12 | context_menu_handler: cef_context_menu_handler_t, 13 | ref_count: AtomicUsize, 14 | } 15 | 16 | impl ContextMenuHandler { 17 | pub fn inc_ref(&self) { 18 | self.ref_count.fetch_add(1, Ordering::SeqCst); 19 | } 20 | } 21 | 22 | extern "C" fn on_before_context_menu( 23 | _slf: *mut cef_context_menu_handler_t, 24 | _browser: *mut cef_browser_t, 25 | _frame: *mut cef_frame_t, 26 | _params: *mut cef_context_menu_params_t, 27 | model: *mut cef_menu_model_t, 28 | ) { 29 | unsafe { 30 | (*model).clear.unwrap()(model); 31 | //(*model).remove.unwrap()(model, super::bindings::cef_menu_id_t_MENU_ID_VIEW_SOURCE as i32); 32 | } 33 | } 34 | 35 | pub fn allocate() -> *mut ContextMenuHandler { 36 | let handler = ContextMenuHandler { 37 | context_menu_handler: cef_context_menu_handler_t { 38 | base: cef_base_ref_counted_t { 39 | size: size_of::() as u64, 40 | add_ref: Some(add_ref), 41 | release: Some(release), 42 | has_one_ref: Some(has_one_ref), 43 | has_at_least_one_ref: Some(has_at_least_one_ref), 44 | }, 45 | on_before_context_menu: Some(on_before_context_menu), 46 | run_context_menu: None, 47 | on_context_menu_command: None, 48 | on_context_menu_dismissed: None, 49 | }, 50 | ref_count: AtomicUsize::new(1), 51 | }; 52 | 53 | Box::into_raw(Box::from(handler)) 54 | } 55 | 56 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 57 | let context_menu_handler = base as *mut ContextMenuHandler; 58 | unsafe { 59 | (*context_menu_handler) 60 | .ref_count 61 | .fetch_add(1, Ordering::SeqCst); 62 | } 63 | } 64 | 65 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 66 | let context_menu_handler = base as *mut ContextMenuHandler; 67 | let count = unsafe { 68 | (*context_menu_handler) 69 | .ref_count 70 | .fetch_sub(1, Ordering::SeqCst) 71 | - 1 72 | }; 73 | 74 | if count == 0 { 75 | unsafe { 76 | Box::from_raw(context_menu_handler); 77 | } 78 | 1 79 | } else { 80 | 0 81 | } 82 | } 83 | 84 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 85 | let context_menu_handler = base as *mut ContextMenuHandler; 86 | let count = unsafe { (*context_menu_handler).ref_count.load(Ordering::SeqCst) }; 87 | if count == 1 { 88 | 1 89 | } else { 90 | 0 91 | } 92 | } 93 | 94 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 95 | let context_menu_handler = base as *mut ContextMenuHandler; 96 | let count = unsafe { (*context_menu_handler).ref_count.load(Ordering::SeqCst) }; 97 | if count >= 1 { 98 | 1 99 | } else { 100 | 0 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/imp/browser_process_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{cef_base_ref_counted_t, cef_browser_process_handler_t, cef_print_handler_t}; 6 | use super::print_handler::{self, PrintHandler}; 7 | 8 | #[repr(C)] 9 | pub struct BrowserProcessHandler { 10 | handler: cef_browser_process_handler_t, 11 | ref_count: AtomicUsize, 12 | print_handler: *mut PrintHandler, 13 | } 14 | 15 | impl BrowserProcessHandler { 16 | pub fn inc_ref(&self) { 17 | self.ref_count.fetch_add(1, Ordering::SeqCst); 18 | } 19 | } 20 | 21 | unsafe extern "C" fn get_print_handler( 22 | slf: *mut cef_browser_process_handler_t, 23 | ) -> *mut cef_print_handler_t { 24 | let _self = slf as *mut BrowserProcessHandler; 25 | (*_self).print_handler as *mut cef_print_handler_t 26 | } 27 | 28 | pub fn allocate() -> *mut BrowserProcessHandler { 29 | let handler = BrowserProcessHandler { 30 | handler: cef_browser_process_handler_t { 31 | base: cef_base_ref_counted_t { 32 | size: size_of::() as u64, 33 | add_ref: Some(add_ref), 34 | release: Some(release), 35 | has_one_ref: Some(has_one_ref), 36 | has_at_least_one_ref: Some(has_at_least_one_ref), 37 | }, 38 | on_context_initialized: None, 39 | on_before_child_process_launch: None, 40 | on_render_process_thread_created: None, 41 | get_print_handler: Some(get_print_handler), 42 | on_schedule_message_pump_work: None, 43 | }, 44 | ref_count: AtomicUsize::new(1), 45 | print_handler: print_handler::allocate(), 46 | }; 47 | 48 | Box::into_raw(Box::from(handler)) 49 | } 50 | 51 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 52 | let browser_process_handler = base as *mut BrowserProcessHandler; 53 | unsafe { 54 | (*browser_process_handler) 55 | .ref_count 56 | .fetch_add(1, Ordering::SeqCst); 57 | } 58 | } 59 | 60 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 61 | let browser_process_handler = base as *mut BrowserProcessHandler; 62 | let count = unsafe { 63 | (*browser_process_handler) 64 | .ref_count 65 | .fetch_sub(1, Ordering::SeqCst) 66 | - 1 67 | }; 68 | 69 | if count == 0 { 70 | unsafe { 71 | let app: Box = Box::from_raw(browser_process_handler); 72 | // TODO: free our handlers here too 73 | Box::from_raw(app.print_handler); 74 | } 75 | 1 76 | } else { 77 | 0 78 | } 79 | } 80 | 81 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 82 | let browser_process_handler = base as *mut BrowserProcessHandler; 83 | let count = unsafe { (*browser_process_handler).ref_count.load(Ordering::SeqCst) }; 84 | if count == 1 { 85 | 1 86 | } else { 87 | 0 88 | } 89 | } 90 | 91 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 92 | let browser_process_handler = base as *mut BrowserProcessHandler; 93 | let count = unsafe { (*browser_process_handler).ref_count.load(Ordering::SeqCst) }; 94 | if count >= 1 { 95 | 1 96 | } else { 97 | 0 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/imp/app.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::sync::atomic::{AtomicUsize, Ordering}; 3 | 4 | use super::bindings::{ 5 | cef_app_t, cef_base_ref_counted_t, cef_browser_process_handler_t, cef_render_process_handler_t, 6 | cef_string_t, cef_command_line_t, 7 | }; 8 | use super::browser_process_handler::{self, BrowserProcessHandler}; 9 | use super::render_process_handler::{self, RenderProcessHandler}; 10 | 11 | #[repr(C)] 12 | pub struct App { 13 | app: cef_app_t, 14 | ref_count: AtomicUsize, 15 | browser_process_handler: *mut BrowserProcessHandler, 16 | render_process_handler: *mut RenderProcessHandler, 17 | } 18 | 19 | impl App { 20 | pub fn inc_ref(&self) { 21 | self.ref_count.fetch_add(1, Ordering::SeqCst); 22 | } 23 | } 24 | 25 | unsafe extern "C" fn on_before_command_line_processing( 26 | _slf: *mut cef_app_t, 27 | _process_type: *const cef_string_t, 28 | command_line: *mut cef_command_line_t 29 | ) { 30 | // append this command line switch to get the on-screen-keyboard working 31 | let mut cef_disable_usb_kb_detect = cef_string_t::default(); 32 | let disable_usb_kb_detect = "disable-usb-keyboard-detect".as_bytes(); 33 | let disable_usb_kb_detect = std::ffi::CString::new(disable_usb_kb_detect).unwrap(); 34 | super::bindings::cef_string_utf8_to_utf16( 35 | disable_usb_kb_detect.as_ptr(), 36 | disable_usb_kb_detect.to_bytes().len() as u64, 37 | &mut cef_disable_usb_kb_detect, 38 | ); 39 | 40 | (*command_line).append_switch.expect("append_switch is a function")(command_line, &cef_disable_usb_kb_detect); 41 | } 42 | 43 | extern "C" fn get_browser_process_handler( 44 | slf: *mut cef_app_t, 45 | ) -> *mut cef_browser_process_handler_t { 46 | let app = slf as *mut App; 47 | let handler = unsafe { (*app).browser_process_handler }; 48 | unsafe { (*handler).inc_ref() }; 49 | handler as *mut cef_browser_process_handler_t 50 | } 51 | 52 | extern "C" fn get_render_process_handler(slf: *mut cef_app_t) -> *mut cef_render_process_handler_t { 53 | let app = slf as *mut App; 54 | let handler = unsafe { (*app).render_process_handler }; 55 | unsafe { (*handler).inc_ref() }; 56 | handler as *mut cef_render_process_handler_t 57 | } 58 | 59 | pub fn allocate() -> *mut App { 60 | let app = App { 61 | app: cef_app_t { 62 | base: cef_base_ref_counted_t { 63 | size: size_of::() as u64, 64 | add_ref: Some(add_ref), 65 | release: Some(release), 66 | has_one_ref: Some(has_one_ref), 67 | has_at_least_one_ref: Some(has_at_least_one_ref), 68 | }, 69 | on_before_command_line_processing: Some(on_before_command_line_processing), 70 | on_register_custom_schemes: None, 71 | get_resource_bundle_handler: None, 72 | get_browser_process_handler: Some(get_browser_process_handler), 73 | get_render_process_handler: Some(get_render_process_handler), 74 | }, 75 | ref_count: AtomicUsize::new(1), 76 | browser_process_handler: browser_process_handler::allocate(), 77 | render_process_handler: render_process_handler::allocate(), 78 | }; 79 | 80 | Box::into_raw(Box::from(app)) 81 | } 82 | 83 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 84 | let app = base as *mut App; 85 | unsafe { 86 | (*app).ref_count.fetch_add(1, Ordering::SeqCst); 87 | } 88 | } 89 | 90 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> i32 { 91 | let app = base as *mut App; 92 | let count = unsafe { (*app).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 93 | 94 | if count == 0 { 95 | unsafe { 96 | let app: Box = Box::from_raw(app as *mut App); 97 | // TODO: free our handlers here too 98 | Box::from_raw(app.browser_process_handler); 99 | Box::from_raw(app.render_process_handler); 100 | } 101 | 1 102 | } else { 103 | 0 104 | } 105 | } 106 | 107 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> i32 { 108 | let app = base as *mut App; 109 | let count = unsafe { (*app).ref_count.load(Ordering::SeqCst) }; 110 | if count == 1 { 111 | 1 112 | } else { 113 | 0 114 | } 115 | } 116 | 117 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> i32 { 118 | let app = base as *mut App; 119 | let count = unsafe { (*app).ref_count.load(Ordering::SeqCst) }; 120 | if count >= 1 { 121 | 1 122 | } else { 123 | 0 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/imp/run_file_dialog_callback.rs: -------------------------------------------------------------------------------- 1 | use super::bindings::{ 2 | _cef_run_file_dialog_callback_t, cef_base_ref_counted_t, cef_string_list_size, 3 | cef_string_list_t, cef_string_list_value, cef_string_userfree_t, 4 | cef_string_userfree_utf16_alloc, cef_string_userfree_utf16_free, 5 | }; 6 | use std::mem::size_of; 7 | use std::os::raw::c_int; 8 | use std::sync::atomic::{AtomicUsize, Ordering}; 9 | 10 | #[repr(C)] 11 | pub struct RunFileDialogCallback { 12 | run_file_dialog_callback: _cef_run_file_dialog_callback_t, 13 | ref_count: AtomicUsize, 14 | on_done: Option)>>, 15 | } 16 | 17 | unsafe extern "C" fn on_file_dialog_dismissed( 18 | slf: *mut _cef_run_file_dialog_callback_t, 19 | _selected_accept_filter: c_int, 20 | file_paths: cef_string_list_t, 21 | ) { 22 | let callback = slf as *mut RunFileDialogCallback; 23 | if let Some(on_done) = &mut (*callback).on_done { 24 | // if they cancelled, file_paths will be null, so alert as much 25 | if file_paths == std::ptr::null_mut() || cef_string_list_size(file_paths) < 1 { 26 | log::debug!("user cancelled file dialog"); 27 | on_done(None); 28 | } else { 29 | // extract the first string from the list (only support a single string for now) 30 | let cef_path: cef_string_userfree_t = cef_string_userfree_utf16_alloc(); 31 | if cef_string_list_value(file_paths, 0, cef_path) != 1 { 32 | log::warn!("failed to extract first path from file dialog callback"); 33 | on_done(None); 34 | return; 35 | } 36 | 37 | // convert the path into a Rust string 38 | let chars: *mut u16 = (*cef_path).str_; 39 | let len: usize = (*cef_path).length as usize; 40 | let chars = std::slice::from_raw_parts(chars, len); 41 | let path = std::char::decode_utf16(chars.iter().cloned()) 42 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 43 | .collect::(); 44 | cef_string_userfree_utf16_free(cef_path); 45 | 46 | // and alert our listener 47 | log::debug!("file dialog complete, path: {}", path); 48 | on_done(Some(std::path::PathBuf::from(path))); 49 | } 50 | } else { 51 | log::warn!("no callback registered for run file dialog callback, is this intentional?"); 52 | } 53 | } 54 | 55 | pub fn allocate( 56 | on_done: Option)>>, 57 | ) -> *mut RunFileDialogCallback { 58 | let handler = RunFileDialogCallback { 59 | run_file_dialog_callback: _cef_run_file_dialog_callback_t { 60 | base: cef_base_ref_counted_t { 61 | size: size_of::() as u64, 62 | add_ref: Some(add_ref_run_file_dialog_callback), 63 | release: Some(release_run_file_dialog_callback), 64 | has_one_ref: Some(has_one_ref_run_file_dialog_callback), 65 | has_at_least_one_ref: Some(has_at_least_one_ref_run_file_dialog_callback), 66 | }, 67 | on_file_dialog_dismissed: Some(on_file_dialog_dismissed), 68 | }, 69 | ref_count: AtomicUsize::new(1), 70 | on_done, 71 | }; 72 | 73 | Box::into_raw(Box::from(handler)) 74 | } 75 | 76 | extern "C" fn add_ref_run_file_dialog_callback(base: *mut cef_base_ref_counted_t) { 77 | let life_span_handler = base as *mut RunFileDialogCallback; 78 | unsafe { 79 | (*life_span_handler) 80 | .ref_count 81 | .fetch_add(1, Ordering::SeqCst); 82 | } 83 | } 84 | 85 | extern "C" fn release_run_file_dialog_callback(base: *mut cef_base_ref_counted_t) -> c_int { 86 | let life_span_handler = base as *mut RunFileDialogCallback; 87 | let count = unsafe { 88 | (*life_span_handler) 89 | .ref_count 90 | .fetch_sub(1, Ordering::SeqCst) 91 | - 1 92 | }; 93 | 94 | if count == 0 { 95 | unsafe { 96 | Box::from_raw(life_span_handler); 97 | } 98 | 1 99 | } else { 100 | 0 101 | } 102 | } 103 | 104 | extern "C" fn has_one_ref_run_file_dialog_callback(base: *mut cef_base_ref_counted_t) -> c_int { 105 | let life_span_handler = base as *mut RunFileDialogCallback; 106 | let count = unsafe { (*life_span_handler).ref_count.load(Ordering::SeqCst) }; 107 | if count == 1 { 108 | 1 109 | } else { 110 | 0 111 | } 112 | } 113 | 114 | extern "C" fn has_at_least_one_ref_run_file_dialog_callback( 115 | base: *mut cef_base_ref_counted_t, 116 | ) -> c_int { 117 | let life_span_handler = base as *mut RunFileDialogCallback; 118 | let count = unsafe { (*life_span_handler).ref_count.load(Ordering::SeqCst) }; 119 | if count >= 1 { 120 | 1 121 | } else { 122 | 0 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::ptr::null_mut; 3 | 4 | mod imp; 5 | use imp::bindings::{ 6 | cef_app_t, cef_execute_process, cef_initialize, cef_log_severity_t_LOGSEVERITY_ERROR, 7 | cef_log_severity_t_LOGSEVERITY_INFO, cef_main_args_t, cef_run_message_loop, cef_settings_t, 8 | cef_shutdown, cef_window_create_top_level, cef_window_delegate_t, 9 | }; 10 | pub use imp::window_delegate::WindowOptions; 11 | use imp::{app, window_delegate}; 12 | 13 | pub struct Cef {} 14 | 15 | impl Cef { 16 | #[cfg(unix)] 17 | pub fn initialize( 18 | debug_port: Option, 19 | disable_command_line_args: bool, 20 | ) -> Result> { 21 | use std::ffi::CString; 22 | use std::os::raw::{c_char, c_int}; 23 | let args: Vec = std::env::args().map(|x| CString::new(x).unwrap()).collect(); 24 | let args: Vec<*mut c_char> = args.iter().map(|x| x.as_ptr() as *mut c_char).collect(); 25 | let main_args = cef_main_args_t { 26 | argc: args.len() as c_int, 27 | argv: args.as_ptr() as *mut *mut c_char, 28 | }; 29 | 30 | log::debug!("preparing app"); 31 | let app = app::allocate(); 32 | 33 | log::debug!("executing process"); 34 | let exit_code = unsafe { 35 | (*app).inc_ref(); 36 | cef_execute_process(&main_args, app as *mut cef_app_t, null_mut()) 37 | }; 38 | if exit_code >= 0 { 39 | std::process::exit(exit_code); 40 | } 41 | 42 | let mut settings = cef_settings_t::default(); 43 | settings.size = size_of::() as u64; 44 | settings.no_sandbox = 1; 45 | if let Some(port) = debug_port { 46 | settings.remote_debugging_port = port as i32; 47 | } 48 | settings.command_line_args_disabled = if disable_command_line_args { 1 } else { 0 }; 49 | if cfg!(debug_assertions) { 50 | settings.log_severity = cef_log_severity_t_LOGSEVERITY_INFO; 51 | } else { 52 | settings.log_severity = cef_log_severity_t_LOGSEVERITY_ERROR; 53 | } 54 | 55 | log::debug!("initializing"); 56 | unsafe { 57 | (*app).inc_ref(); 58 | if cef_initialize(&main_args, &settings, app as *mut cef_app_t, null_mut()) != 1 { 59 | return Err(Box::from("failed to initialize")); 60 | } 61 | } 62 | 63 | Ok(Cef {}) 64 | } 65 | 66 | #[cfg(windows)] 67 | pub fn initialize( 68 | debug_port: Option, 69 | disable_command_line_args: bool, 70 | ) -> Result> { 71 | let main_args = unsafe { 72 | cef_main_args_t { 73 | instance: winapi::um::libloaderapi::GetModuleHandleA(null_mut()) 74 | as imp::bindings::HINSTANCE, 75 | } 76 | }; 77 | 78 | log::debug!("preparing app"); 79 | let app = app::allocate(); 80 | 81 | log::debug!("executing process"); 82 | let exit_code = unsafe { 83 | (*app).inc_ref(); 84 | cef_execute_process(&main_args, app as *mut cef_app_t, null_mut()) 85 | }; 86 | if exit_code >= 0 { 87 | std::process::exit(exit_code); 88 | } 89 | 90 | let mut settings = cef_settings_t::default(); 91 | settings.size = size_of::() as u64; 92 | settings.no_sandbox = 1; 93 | if let Some(port) = debug_port { 94 | settings.remote_debugging_port = port as i32; 95 | } 96 | settings.command_line_args_disabled = if disable_command_line_args { 1 } else { 0 }; 97 | if cfg!(debug_assertions) { 98 | settings.log_severity = cef_log_severity_t_LOGSEVERITY_INFO; 99 | } else { 100 | settings.log_severity = cef_log_severity_t_LOGSEVERITY_ERROR; 101 | } 102 | 103 | log::debug!("initializing"); 104 | unsafe { 105 | (*app).inc_ref(); 106 | if cef_initialize(&main_args, &settings, app as *mut cef_app_t, null_mut()) != 1 { 107 | return Err(Box::from("failed to initialize")); 108 | } 109 | } 110 | 111 | Ok(Cef {}) 112 | } 113 | 114 | pub fn open_window(&self, options: WindowOptions) -> Result<(), Box> { 115 | let window_delegate = unsafe { window_delegate::allocate(options) }; 116 | log::debug!("creating window"); 117 | let _window = unsafe { 118 | (*window_delegate).inc_ref(); 119 | cef_window_create_top_level(window_delegate as *mut cef_window_delegate_t) 120 | }; 121 | 122 | Ok(()) 123 | } 124 | 125 | pub fn run(&self) -> Result<(), Box> { 126 | log::debug!("running message loop"); 127 | unsafe { cef_run_message_loop() }; 128 | 129 | log::debug!("shutting down"); 130 | unsafe { cef_shutdown() }; 131 | 132 | Ok(()) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/imp/display_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_display_handler_t, cef_log_severity_t, 7 | cef_log_severity_t_LOGSEVERITY_DEBUG, cef_log_severity_t_LOGSEVERITY_DEFAULT, 8 | cef_log_severity_t_LOGSEVERITY_ERROR, cef_log_severity_t_LOGSEVERITY_FATAL, 9 | cef_log_severity_t_LOGSEVERITY_INFO, cef_log_severity_t_LOGSEVERITY_WARNING, cef_string_t, 10 | cef_window_t, 11 | }; 12 | 13 | #[repr(C)] 14 | pub struct DisplayHandler { 15 | display_handler: cef_display_handler_t, 16 | ref_count: AtomicUsize, 17 | window: *mut cef_window_t, 18 | } 19 | 20 | impl DisplayHandler { 21 | pub fn inc_ref(&self) { 22 | self.ref_count.fetch_add(1, Ordering::SeqCst); 23 | } 24 | } 25 | 26 | unsafe extern "C" fn on_fullscreen_mode_change( 27 | slf: *mut cef_display_handler_t, 28 | _browser: *mut cef_browser_t, 29 | fullscreen: i32, 30 | ) { 31 | let handler = slf as *mut DisplayHandler; 32 | (*(*handler).window) 33 | .set_fullscreen 34 | .expect("set_fullscreen exists")((*handler).window, fullscreen); 35 | } 36 | 37 | unsafe extern "C" fn on_tooltip( 38 | _slf: *mut cef_display_handler_t, 39 | _browser: *mut cef_browser_t, 40 | _text: *mut cef_string_t, 41 | ) -> i32 { 42 | 1 43 | } 44 | 45 | extern "C" fn on_console_message( 46 | _slf: *mut cef_display_handler_t, 47 | _browser: *mut cef_browser_t, 48 | level: cef_log_severity_t, 49 | message: *const cef_string_t, 50 | _source: *const cef_string_t, 51 | _line: i32, 52 | ) -> i32 { 53 | let chars: *mut u16 = unsafe { (*message).str_ }; 54 | let len: usize = unsafe { (*message).length } as usize; 55 | let chars = unsafe { std::slice::from_raw_parts(chars, len) }; 56 | let message = std::char::decode_utf16(chars.iter().cloned()) 57 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 58 | .collect::(); 59 | 60 | #[allow(non_upper_case_globals)] 61 | match level { 62 | cef_log_severity_t_LOGSEVERITY_DEFAULT => log::info!("[CONSOLE] {}", message), 63 | cef_log_severity_t_LOGSEVERITY_DEBUG => log::debug!("[CONSOLE] {}", message), 64 | cef_log_severity_t_LOGSEVERITY_INFO => log::info!("[CONSOLE] {}", message), 65 | cef_log_severity_t_LOGSEVERITY_WARNING => log::warn!("[CONSOLE] {}", message), 66 | cef_log_severity_t_LOGSEVERITY_ERROR => log::error!("[CONSOLE] {}", message), 67 | cef_log_severity_t_LOGSEVERITY_FATAL => log::error!("[CONSOLE] {}", message), 68 | _ => log::info!("[CONSOLE] {}", message), 69 | } 70 | 71 | 1 72 | } 73 | 74 | pub fn allocate(window: *mut cef_window_t) -> *mut DisplayHandler { 75 | let handler = DisplayHandler { 76 | display_handler: cef_display_handler_t { 77 | base: cef_base_ref_counted_t { 78 | size: size_of::() as u64, 79 | add_ref: Some(add_ref), 80 | release: Some(release), 81 | has_one_ref: Some(has_one_ref), 82 | has_at_least_one_ref: Some(has_at_least_one_ref), 83 | }, 84 | on_address_change: None, 85 | on_title_change: None, 86 | on_favicon_urlchange: None, 87 | on_fullscreen_mode_change: Some(on_fullscreen_mode_change), 88 | on_tooltip: Some(on_tooltip), 89 | on_status_message: None, 90 | on_console_message: Some(on_console_message), 91 | on_auto_resize: None, 92 | on_loading_progress_change: None, 93 | }, 94 | window, 95 | ref_count: AtomicUsize::new(1), 96 | }; 97 | 98 | Box::into_raw(Box::from(handler)) 99 | } 100 | 101 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 102 | let display_handler = base as *mut DisplayHandler; 103 | unsafe { (*display_handler).ref_count.fetch_add(1, Ordering::SeqCst) }; 104 | } 105 | 106 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 107 | let display_handler = base as *mut DisplayHandler; 108 | let count = unsafe { (*display_handler).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 109 | 110 | if count == 0 { 111 | unsafe { 112 | Box::from_raw(display_handler); 113 | } 114 | 1 115 | } else { 116 | 0 117 | } 118 | } 119 | 120 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 121 | let display_handler = base as *mut DisplayHandler; 122 | let count = unsafe { (*display_handler).ref_count.load(Ordering::SeqCst) }; 123 | if count == 1 { 124 | 1 125 | } else { 126 | 0 127 | } 128 | } 129 | 130 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 131 | let display_handler = base as *mut DisplayHandler; 132 | let count = unsafe { (*display_handler).ref_count.load(Ordering::SeqCst) }; 133 | if count >= 1 { 134 | 1 135 | } else { 136 | 0 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/imp/browser.rs: -------------------------------------------------------------------------------- 1 | use super::bindings::{ 2 | cef_browser_t, cef_string_list_alloc, cef_string_list_append, cef_string_t, 3 | cef_string_utf8_to_utf16, 4 | }; 5 | use super::print_pdf_callback; 6 | use super::run_file_dialog_callback; 7 | use std::ffi::CString; 8 | 9 | pub unsafe fn print_to_pdf>( 10 | browser: *mut cef_browser_t, 11 | path: P, 12 | on_done: Option>, 13 | ) { 14 | log::debug!("printing PDF to path `{}`...", path.as_ref().display()); 15 | 16 | // get our browser host 17 | let host = (*browser).get_host.unwrap()(browser); 18 | 19 | // first, convert the path to a cef string 20 | let path: String = path.as_ref().display().to_string(); 21 | let path = path.as_bytes(); 22 | let path = CString::new(path).unwrap(); 23 | let mut cef_path = cef_string_t::default(); 24 | cef_string_utf8_to_utf16(path.as_ptr(), path.to_bytes().len() as u64, &mut cef_path); 25 | 26 | // determine the settings 27 | // note: page size in microns, to get microns from inches, multiply 28 | // by 25400. 29 | // TODO: different paper sizes? 30 | let settings = super::bindings::_cef_pdf_print_settings_t { 31 | header_footer_title: cef_string_t::default(), // empty header / footer 32 | header_footer_url: cef_string_t::default(), // empty url 33 | page_width: 210000, // 210 mm (a4 paper) 34 | page_height: 297000, // 297 mm (a4 paper) 35 | scale_factor: 100, // scale the page at 100% (i.e. don't.) 36 | margin_top: 0.0, // margins in millimeters (actually ignored because of margin type) 37 | margin_right: 0.0, 38 | margin_bottom: 0.0, 39 | margin_left: 0.0, 40 | margin_type: super::bindings::cef_pdf_print_margin_type_t_PDF_PRINT_MARGIN_DEFAULT, // default margins as defined by chrome, ~1 inch 41 | header_footer_enabled: 0, // no headers or footers 42 | selection_only: 0, // print everything 43 | landscape: 0, // portrait mode 44 | backgrounds_enabled: 1, // show background colours / graphics 45 | }; 46 | 47 | // now a callback when the print is done 48 | let callback = print_pdf_callback::allocate(on_done); 49 | 50 | // finally, initiate the print 51 | (*host).print_to_pdf.expect("print_to_pdf is a function")( 52 | host, 53 | &mut cef_path, 54 | &settings, 55 | callback as *mut super::bindings::_cef_pdf_print_callback_t, 56 | ); 57 | } 58 | 59 | pub unsafe fn run_file_dialog( 60 | browser: *mut cef_browser_t, 61 | mode: super::v8_file_dialog_handler::FileDialogMode, 62 | title: String, 63 | initial_file_name: String, 64 | filter: String, 65 | on_done: Option)>>, 66 | ) { 67 | log::debug!("launching file dialog in mode `{:?}`...", mode); 68 | 69 | // get our browser host 70 | let host = (*browser).get_host.unwrap()(browser); 71 | 72 | // convert the title to a cef string 73 | let title = title.as_bytes(); 74 | let title = CString::new(title).unwrap(); 75 | let mut cef_title = cef_string_t::default(); 76 | cef_string_utf8_to_utf16( 77 | title.as_ptr(), 78 | title.to_bytes().len() as u64, 79 | &mut cef_title, 80 | ); 81 | 82 | // convert the initial_file_name to a cef string 83 | let initial_file_name = initial_file_name.as_bytes(); 84 | let initial_file_name = CString::new(initial_file_name).unwrap(); 85 | let mut cef_initial_file_name = cef_string_t::default(); 86 | cef_string_utf8_to_utf16( 87 | initial_file_name.as_ptr(), 88 | initial_file_name.to_bytes().len() as u64, 89 | &mut cef_initial_file_name, 90 | ); 91 | 92 | // convert the filter to a cef string 93 | let filter = filter.as_bytes(); 94 | let filter = CString::new(filter).unwrap(); 95 | let mut cef_filter = cef_string_t::default(); 96 | cef_string_utf8_to_utf16( 97 | filter.as_ptr(), 98 | filter.to_bytes().len() as u64, 99 | &mut cef_filter, 100 | ); 101 | 102 | // build the filter list 103 | let filters = cef_string_list_alloc(); 104 | cef_string_list_append(filters, &cef_filter); 105 | 106 | // and a callback 107 | let callback = run_file_dialog_callback::allocate(on_done); 108 | 109 | // and run the dialog 110 | (*host) 111 | .run_file_dialog 112 | .expect("run_file_dialog is a function")( 113 | host, 114 | match mode { 115 | super::v8_file_dialog_handler::FileDialogMode::Open => { 116 | super::bindings::cef_file_dialog_mode_t_FILE_DIALOG_OPEN 117 | } 118 | super::v8_file_dialog_handler::FileDialogMode::Save => { 119 | super::bindings::cef_file_dialog_mode_t_FILE_DIALOG_SAVE 120 | } 121 | }, 122 | &cef_title, 123 | &cef_initial_file_name, 124 | filters, 125 | 0, 126 | callback as *mut super::bindings::_cef_run_file_dialog_callback_t, 127 | ); 128 | } 129 | -------------------------------------------------------------------------------- /src/imp/render_process_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_dictionary_value_t, cef_frame_t, cef_process_id_t, 7 | cef_process_message_t, cef_render_process_handler_t, cef_string_userfree_t, 8 | cef_string_userfree_utf16_free, 9 | }; 10 | use super::v8_file_dialog_handler::{self, V8FileDialogHandler}; 11 | use super::v8_pdf_print_handler::{self, V8PDFPrintHandler}; 12 | 13 | #[repr(C)] 14 | pub struct RenderProcessHandler { 15 | render_process_handler: cef_render_process_handler_t, 16 | ref_count: AtomicUsize, 17 | pdf_print_extension: *mut V8PDFPrintHandler, 18 | file_dialog_extension: *mut V8FileDialogHandler, 19 | } 20 | 21 | impl RenderProcessHandler { 22 | pub fn inc_ref(&self) { 23 | self.ref_count.fetch_add(1, Ordering::SeqCst); 24 | } 25 | } 26 | 27 | unsafe extern "C" fn on_web_kit_initialized(slf: *mut cef_render_process_handler_t) { 28 | let _self = slf as *mut RenderProcessHandler; 29 | super::v8_pdf_print_handler::register_extension((*_self).pdf_print_extension); 30 | super::v8_file_dialog_handler::register_extension((*_self).file_dialog_extension); 31 | log::debug!("web kit initialized"); 32 | } 33 | 34 | unsafe extern "C" fn on_browser_created( 35 | slf: *mut cef_render_process_handler_t, 36 | browser: *mut cef_browser_t, 37 | _extra_info: *mut cef_dictionary_value_t, 38 | ) { 39 | log::debug!("browser created"); 40 | let _self = slf as *mut RenderProcessHandler; 41 | (*(*_self).pdf_print_extension).browser = Some(browser); 42 | (*(*_self).file_dialog_extension).browser = Some(browser); 43 | } 44 | 45 | unsafe extern "C" fn on_browser_destroyed( 46 | slf: *mut cef_render_process_handler_t, 47 | _browser: *mut cef_browser_t, 48 | ) { 49 | log::debug!("browser destroyed"); 50 | let _self = slf as *mut RenderProcessHandler; 51 | (*(*_self).pdf_print_extension).browser = None; 52 | (*(*_self).file_dialog_extension).browser = None; 53 | } 54 | 55 | unsafe extern "C" fn on_process_message_received( 56 | slf: *mut cef_render_process_handler_t, 57 | _browser: *mut cef_browser_t, 58 | _frame: *mut cef_frame_t, 59 | _source_process: cef_process_id_t, 60 | message: *mut cef_process_message_t, 61 | ) -> c_int { 62 | let cef_message_name: cef_string_userfree_t = 63 | ((*message).get_name.expect("get_name is a function"))(message); 64 | let chars: *mut u16 = (*cef_message_name).str_; 65 | let len: usize = (*cef_message_name).length as usize; 66 | let chars = std::slice::from_raw_parts(chars, len); 67 | let message_name = std::char::decode_utf16(chars.iter().cloned()) 68 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 69 | .collect::(); 70 | cef_string_userfree_utf16_free(cef_message_name); 71 | log::debug!("renderer received message: {}", message_name); 72 | 73 | let _self = slf as *mut RenderProcessHandler; 74 | if super::v8_pdf_print_handler::process_message( 75 | (*_self).pdf_print_extension, 76 | &message_name, 77 | message, 78 | ) { 79 | return 1; 80 | } 81 | if super::v8_file_dialog_handler::process_message( 82 | (*_self).file_dialog_extension, 83 | &message_name, 84 | message, 85 | ) { 86 | return 1; 87 | } 88 | log::warn!("unhandled process message in renderer: `{}`", message_name); 89 | 0 90 | } 91 | 92 | pub fn allocate() -> *mut RenderProcessHandler { 93 | let handler = RenderProcessHandler { 94 | render_process_handler: cef_render_process_handler_t { 95 | base: cef_base_ref_counted_t { 96 | size: size_of::() as u64, 97 | add_ref: Some(add_ref), 98 | release: Some(release), 99 | has_one_ref: Some(has_one_ref), 100 | has_at_least_one_ref: Some(has_at_least_one_ref), 101 | }, 102 | on_render_thread_created: None, 103 | on_web_kit_initialized: Some(on_web_kit_initialized), 104 | on_browser_created: Some(on_browser_created), 105 | on_browser_destroyed: Some(on_browser_destroyed), 106 | get_load_handler: None, 107 | on_context_created: None, 108 | on_context_released: None, 109 | on_uncaught_exception: None, 110 | on_focused_node_changed: None, 111 | on_process_message_received: Some(on_process_message_received), 112 | }, 113 | ref_count: AtomicUsize::new(1), 114 | pdf_print_extension: v8_pdf_print_handler::allocate(), 115 | file_dialog_extension: v8_file_dialog_handler::allocate(), 116 | }; 117 | 118 | Box::into_raw(Box::from(handler)) 119 | } 120 | 121 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 122 | let render_process_handler = base as *mut RenderProcessHandler; 123 | unsafe { 124 | (*render_process_handler) 125 | .ref_count 126 | .fetch_add(1, Ordering::SeqCst) 127 | }; 128 | } 129 | 130 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 131 | let render_process_handler = base as *mut RenderProcessHandler; 132 | let count = unsafe { 133 | (*render_process_handler) 134 | .ref_count 135 | .fetch_sub(1, Ordering::SeqCst) 136 | - 1 137 | }; 138 | 139 | if count == 0 { 140 | unsafe { 141 | Box::from_raw(render_process_handler); 142 | } 143 | 1 144 | } else { 145 | 0 146 | } 147 | } 148 | 149 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 150 | let render_process_handler = base as *mut RenderProcessHandler; 151 | let count = unsafe { (*render_process_handler).ref_count.load(Ordering::SeqCst) }; 152 | if count == 1 { 153 | 1 154 | } else { 155 | 0 156 | } 157 | } 158 | 159 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 160 | let render_process_handler = base as *mut RenderProcessHandler; 161 | let count = unsafe { (*render_process_handler).ref_count.load(Ordering::SeqCst) }; 162 | if count >= 1 { 163 | 1 164 | } else { 165 | 0 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "lldb", 9 | "request": "launch", 10 | "name": "Debug unit tests in library 'cef-simple'", 11 | "cargo": { 12 | "args": [ 13 | "test", 14 | "--no-run", 15 | "--lib", 16 | "--package=cef-simple" 17 | ], 18 | "filter": { 19 | "name": "cef-simple", 20 | "kind": "lib" 21 | } 22 | }, 23 | "args": [], 24 | "cwd": "${workspaceFolder}" 25 | }, 26 | { 27 | "type": "lldb", 28 | "request": "launch", 29 | "name": "Debug example 'file-dialogs'", 30 | "cargo": { 31 | "args": [ 32 | "build", 33 | "--example=file-dialogs", 34 | "--package=cef-simple" 35 | ], 36 | "filter": { 37 | "name": "file-dialogs", 38 | "kind": "example" 39 | } 40 | }, 41 | "args": [], 42 | "cwd": "${workspaceFolder}" 43 | }, 44 | { 45 | "type": "lldb", 46 | "request": "launch", 47 | "name": "Debug unit tests in example 'file-dialogs'", 48 | "cargo": { 49 | "args": [ 50 | "test", 51 | "--no-run", 52 | "--example=file-dialogs", 53 | "--package=cef-simple" 54 | ], 55 | "filter": { 56 | "name": "file-dialogs", 57 | "kind": "example" 58 | } 59 | }, 60 | "args": [], 61 | "cwd": "${workspaceFolder}" 62 | }, 63 | { 64 | "type": "lldb", 65 | "request": "launch", 66 | "name": "Debug example 'fullscreen'", 67 | "cargo": { 68 | "args": [ 69 | "build", 70 | "--example=fullscreen", 71 | "--package=cef-simple" 72 | ], 73 | "filter": { 74 | "name": "fullscreen", 75 | "kind": "example" 76 | } 77 | }, 78 | "args": [], 79 | "cwd": "${workspaceFolder}" 80 | }, 81 | { 82 | "type": "lldb", 83 | "request": "launch", 84 | "name": "Debug unit tests in example 'fullscreen'", 85 | "cargo": { 86 | "args": [ 87 | "test", 88 | "--no-run", 89 | "--example=fullscreen", 90 | "--package=cef-simple" 91 | ], 92 | "filter": { 93 | "name": "fullscreen", 94 | "kind": "example" 95 | } 96 | }, 97 | "args": [], 98 | "cwd": "${workspaceFolder}" 99 | }, 100 | { 101 | "type": "lldb", 102 | "request": "launch", 103 | "name": "Debug example 'mithril'", 104 | "cargo": { 105 | "args": [ 106 | "build", 107 | "--example=mithril", 108 | "--package=cef-simple" 109 | ], 110 | "filter": { 111 | "name": "mithril", 112 | "kind": "example" 113 | } 114 | }, 115 | "args": [], 116 | "cwd": "${workspaceFolder}" 117 | }, 118 | { 119 | "type": "lldb", 120 | "request": "launch", 121 | "name": "Debug unit tests in example 'mithril'", 122 | "cargo": { 123 | "args": [ 124 | "test", 125 | "--no-run", 126 | "--example=mithril", 127 | "--package=cef-simple" 128 | ], 129 | "filter": { 130 | "name": "mithril", 131 | "kind": "example" 132 | } 133 | }, 134 | "args": [], 135 | "cwd": "${workspaceFolder}" 136 | }, 137 | { 138 | "type": "lldb", 139 | "request": "launch", 140 | "name": "Debug example 'printing'", 141 | "cargo": { 142 | "args": [ 143 | "build", 144 | "--example=printing", 145 | "--package=cef-simple" 146 | ], 147 | "filter": { 148 | "name": "printing", 149 | "kind": "example" 150 | } 151 | }, 152 | "args": [], 153 | "cwd": "${workspaceFolder}" 154 | }, 155 | { 156 | "type": "lldb", 157 | "request": "launch", 158 | "name": "Debug unit tests in example 'printing'", 159 | "cargo": { 160 | "args": [ 161 | "test", 162 | "--no-run", 163 | "--example=printing", 164 | "--package=cef-simple" 165 | ], 166 | "filter": { 167 | "name": "printing", 168 | "kind": "example" 169 | } 170 | }, 171 | "args": [], 172 | "cwd": "${workspaceFolder}" 173 | }, 174 | { 175 | "type": "lldb", 176 | "request": "launch", 177 | "name": "Debug example 'simple'", 178 | "cargo": { 179 | "args": [ 180 | "build", 181 | "--example=simple", 182 | "--package=cef-simple" 183 | ], 184 | "filter": { 185 | "name": "simple", 186 | "kind": "example" 187 | } 188 | }, 189 | "args": [], 190 | "cwd": "${workspaceFolder}" 191 | }, 192 | { 193 | "type": "lldb", 194 | "request": "launch", 195 | "name": "Debug unit tests in example 'simple'", 196 | "cargo": { 197 | "args": [ 198 | "test", 199 | "--no-run", 200 | "--example=simple", 201 | "--package=cef-simple" 202 | ], 203 | "filter": { 204 | "name": "simple", 205 | "kind": "example" 206 | } 207 | }, 208 | "args": [], 209 | "cwd": "${workspaceFolder}" 210 | } 211 | ] 212 | } -------------------------------------------------------------------------------- /src/imp/window_delegate.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | use std::mem::size_of; 3 | use std::os::raw::c_int; 4 | use std::sync::atomic::{AtomicUsize, Ordering}; 5 | 6 | use super::bindings::{ 7 | cef_base_ref_counted_t, cef_browser_settings_t, cef_browser_view_create, 8 | cef_browser_view_delegate_t, cef_client_t, cef_dictionary_value_create, cef_image_create, 9 | cef_image_t, cef_panel_delegate_t, cef_panel_t, cef_request_context_get_global_context, 10 | cef_size_t, cef_state_t_STATE_DISABLED, cef_state_t_STATE_ENABLED, cef_string_t, 11 | cef_string_utf8_to_utf16, cef_view_delegate_t, cef_view_t, cef_window_delegate_t, cef_window_t, 12 | }; 13 | use super::{browser_view_delegate, client}; 14 | 15 | #[derive(Debug)] 16 | pub struct WindowOptions { 17 | pub url: String, 18 | pub title: Option, 19 | pub maximized: bool, 20 | pub fullscreen: bool, 21 | pub size: Option<(i32, i32)>, 22 | pub window_icon: Option<&'static [u8]>, 23 | pub window_app_icon: Option<&'static [u8]>, 24 | } 25 | 26 | impl Default for WindowOptions { 27 | fn default() -> WindowOptions { 28 | WindowOptions { 29 | url: "https://hamaluik.ca/".to_string(), 30 | title: None, 31 | maximized: false, 32 | fullscreen: false, 33 | size: Some((1280, 720)), 34 | window_icon: None, 35 | window_app_icon: None, 36 | } 37 | } 38 | } 39 | 40 | #[derive(Debug)] 41 | #[repr(C)] 42 | pub struct WindowDelegate { 43 | pub window_delegate: cef_window_delegate_t, 44 | pub ref_count: AtomicUsize, 45 | pub options: WindowOptions, 46 | pub window_icon: Option<*mut cef_image_t>, 47 | pub window_app_icon: Option<*mut cef_image_t>, 48 | } 49 | 50 | impl WindowDelegate { 51 | pub fn inc_ref(&self) { 52 | self.ref_count.fetch_add(1, Ordering::SeqCst); 53 | } 54 | } 55 | 56 | extern "C" fn is_frameless(_: *mut cef_window_delegate_t, _: *mut cef_window_t) -> c_int { 57 | 0 58 | } 59 | 60 | extern "C" fn can_resize(_: *mut cef_window_delegate_t, _: *mut cef_window_t) -> c_int { 61 | 1 62 | } 63 | 64 | extern "C" fn can_maximize(_: *mut cef_window_delegate_t, _: *mut cef_window_t) -> c_int { 65 | 1 66 | } 67 | 68 | extern "C" fn can_minimize(_: *mut cef_window_delegate_t, _: *mut cef_window_t) -> c_int { 69 | 1 70 | } 71 | 72 | extern "C" fn can_close(_: *mut cef_window_delegate_t, _: *mut cef_window_t) -> c_int { 73 | 1 74 | } 75 | 76 | extern "C" fn window_delegate_created(slf: *mut cef_window_delegate_t, window: *mut cef_window_t) { 77 | log::debug!("window delegate created!"); 78 | 79 | let window_delegate = slf as *mut WindowDelegate; 80 | let mut cef_url = cef_string_t::default(); 81 | unsafe { 82 | let url = (*window_delegate).options.url.as_bytes(); 83 | let url = CString::new(url).unwrap(); 84 | cef_string_utf8_to_utf16(url.as_ptr(), url.to_bytes().len() as u64, &mut cef_url); 85 | } 86 | 87 | let mut browser_settings = cef_browser_settings_t::default(); 88 | browser_settings.databases = cef_state_t_STATE_DISABLED; 89 | browser_settings.local_storage = cef_state_t_STATE_ENABLED; 90 | browser_settings.application_cache = cef_state_t_STATE_DISABLED; 91 | 92 | let client = client::allocate(window); 93 | let browser_view_delegate = browser_view_delegate::allocate(); 94 | 95 | let browser_view = unsafe { 96 | (*client).inc_ref(); 97 | (*browser_view_delegate).inc_ref(); 98 | cef_browser_view_create( 99 | client as *mut cef_client_t, 100 | &cef_url, 101 | &browser_settings, 102 | cef_dictionary_value_create(), 103 | cef_request_context_get_global_context(), 104 | browser_view_delegate as *mut cef_browser_view_delegate_t, 105 | ) 106 | }; 107 | 108 | unsafe { 109 | (*browser_view).base.base.add_ref.unwrap()(browser_view as *mut cef_base_ref_counted_t); 110 | (*window).base.add_child_view.unwrap()( 111 | window as *mut cef_panel_t, 112 | browser_view as *mut cef_view_t, 113 | ); 114 | 115 | (*window).show.unwrap()(window); 116 | 117 | if let Some(title) = &(*window_delegate).options.title { 118 | let mut cef_title = cef_string_t::default(); 119 | let title = title.as_bytes(); 120 | let title = CString::new(title).unwrap(); 121 | cef_string_utf8_to_utf16( 122 | title.as_ptr(), 123 | title.to_bytes().len() as u64, 124 | &mut cef_title, 125 | ); 126 | 127 | (*window).set_title.unwrap()(window, &cef_title); 128 | } 129 | 130 | if let Some(icon) = (*window_delegate).window_icon { 131 | (*window).set_window_icon.unwrap()(window, icon); 132 | } 133 | 134 | if let Some(icon) = (*window_delegate).window_app_icon { 135 | (*window).set_window_app_icon.unwrap()(window, icon); 136 | } 137 | 138 | if let Some(size) = (*window_delegate).options.size { 139 | let size: cef_size_t = cef_size_t { 140 | width: size.0, 141 | height: size.1, 142 | }; 143 | (*window).center_window.unwrap()(window, &size); 144 | } 145 | 146 | if (*window_delegate).options.maximized { 147 | (*window).maximize.unwrap()(window); 148 | } 149 | 150 | if (*window_delegate).options.fullscreen { 151 | (*window).set_fullscreen.unwrap()(window, 1); 152 | } 153 | } 154 | } 155 | 156 | pub unsafe fn allocate(options: WindowOptions) -> *mut WindowDelegate { 157 | let window_icon = if let Some(data) = options.window_icon { 158 | let image = cef_image_create(); 159 | (*image).add_png.unwrap()(image, 1.0, data.as_ptr() as *const _, data.len() as u64); 160 | Some(image) 161 | } else { 162 | None 163 | }; 164 | 165 | let window_app_icon = if let Some(data) = options.window_app_icon { 166 | let image = cef_image_create(); 167 | (*image).add_png.unwrap()(image, 1.0, data.as_ptr() as *const _, data.len() as u64); 168 | Some(image) 169 | } else { 170 | None 171 | }; 172 | 173 | let window_delegate = WindowDelegate { 174 | window_delegate: cef_window_delegate_t { 175 | base: cef_panel_delegate_t { 176 | base: cef_view_delegate_t { 177 | base: cef_base_ref_counted_t { 178 | size: size_of::() as u64, 179 | add_ref: Some(add_ref), 180 | release: Some(release), 181 | has_one_ref: Some(has_one_ref), 182 | has_at_least_one_ref: Some(has_at_least_one_ref), 183 | }, 184 | get_preferred_size: None, 185 | get_minimum_size: None, 186 | get_maximum_size: None, 187 | get_height_for_width: None, 188 | on_parent_view_changed: None, 189 | on_child_view_changed: None, 190 | on_focus: None, 191 | on_blur: None, 192 | }, 193 | }, 194 | on_window_created: Some(window_delegate_created), 195 | on_window_destroyed: None, 196 | get_parent_window: None, 197 | is_frameless: Some(is_frameless), 198 | can_resize: Some(can_resize), 199 | can_maximize: Some(can_maximize), 200 | can_minimize: Some(can_minimize), 201 | can_close: Some(can_close), 202 | on_accelerator: None, 203 | on_key_event: None, 204 | }, 205 | ref_count: AtomicUsize::new(1), 206 | options, 207 | window_icon, 208 | window_app_icon, 209 | }; 210 | 211 | Box::into_raw(Box::from(window_delegate)) 212 | } 213 | 214 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 215 | let window_delegate = base as *mut WindowDelegate; 216 | unsafe { 217 | (*window_delegate).ref_count.fetch_add(1, Ordering::SeqCst); 218 | } 219 | } 220 | 221 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 222 | let window_delegate = base as *mut WindowDelegate; 223 | let count = unsafe { (*window_delegate).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 224 | 225 | if count == 0 { 226 | unsafe { 227 | Box::from_raw(window_delegate); 228 | } 229 | 1 230 | } else { 231 | 0 232 | } 233 | } 234 | 235 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 236 | let window_delegate = base as *mut WindowDelegate; 237 | let count = unsafe { (*window_delegate).ref_count.load(Ordering::SeqCst) }; 238 | if count == 1 { 239 | 1 240 | } else { 241 | 0 242 | } 243 | } 244 | 245 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 246 | let window_delegate = base as *mut WindowDelegate; 247 | let count = unsafe { (*window_delegate).ref_count.load(Ordering::SeqCst) }; 248 | if count >= 1 { 249 | 1 250 | } else { 251 | 0 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/imp/v8_pdf_print_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_process_message_t, cef_string_t, 7 | cef_string_userfree_t, cef_string_userfree_utf16_free, cef_v8context_get_current_context, 8 | cef_v8context_t, cef_v8handler_t, cef_v8value_t, size_t, 9 | }; 10 | 11 | #[repr(C)] 12 | pub struct V8PDFPrintHandler { 13 | v8_handler: cef_v8handler_t, 14 | ref_count: AtomicUsize, 15 | pub browser: Option<*mut cef_browser_t>, 16 | pub done_callback: Option<(*mut cef_v8context_t, *mut cef_v8value_t, *mut cef_v8value_t)>, 17 | } 18 | 19 | const CODE: &str = r#" 20 | var cef; 21 | if(!cef) cef = {}; 22 | (function() { 23 | cef.printToPDF = function(path) { 24 | native function printToPDF(path, onDone, onError); 25 | return new Promise((resolve, reject) => { 26 | printToPDF(path, resolve, reject); 27 | }); 28 | }; 29 | })(); 30 | "#; 31 | 32 | pub unsafe fn register_extension(extension: *mut V8PDFPrintHandler) { 33 | use super::bindings::{cef_register_extension, cef_string_utf8_to_utf16}; 34 | use std::ffi::CString; 35 | let code = CODE.as_bytes(); 36 | let code = CString::new(code).unwrap(); 37 | let mut cef_code = cef_string_t::default(); 38 | cef_string_utf8_to_utf16(code.as_ptr(), code.to_bytes().len() as u64, &mut cef_code); 39 | 40 | let extension_name = "CEF PDF Printer"; 41 | let extension_name = extension_name.as_bytes(); 42 | let extension_name = CString::new(extension_name).unwrap(); 43 | let mut cef_extension_name = cef_string_t::default(); 44 | cef_string_utf8_to_utf16( 45 | extension_name.as_ptr(), 46 | extension_name.to_bytes().len() as u64, 47 | &mut cef_extension_name, 48 | ); 49 | 50 | cef_register_extension( 51 | &cef_extension_name, 52 | &cef_code, 53 | extension as *mut cef_v8handler_t, 54 | ); 55 | log::debug!("registered pdf printer extension"); 56 | } 57 | 58 | pub unsafe fn process_message( 59 | slf: *mut V8PDFPrintHandler, 60 | message_name: &str, 61 | message: *mut cef_process_message_t, 62 | ) -> bool { 63 | if message_name != "print_to_pdf_done" { 64 | return false; 65 | } 66 | 67 | let args = ((*message) 68 | .get_argument_list 69 | .expect("get_argument_list is a function"))(message); 70 | let ok: bool = ((*args).get_bool.expect("get_bool is a function"))(args, 0) == 1; 71 | on_pdf_print_done(slf, ok); 72 | true 73 | } 74 | 75 | unsafe fn on_pdf_print_done(slf: *mut V8PDFPrintHandler, ok: bool) { 76 | if let Some((context, on_success, on_error)) = (*slf).done_callback { 77 | ((*context).enter.expect("enter is a function"))(context); 78 | 79 | // execute the appropriate callback 80 | if ok { 81 | ((*on_success) 82 | .execute_function 83 | .expect("execute_function is a function"))( 84 | on_success, 85 | std::ptr::null_mut(), 86 | 0, 87 | std::ptr::null_mut(), 88 | ); 89 | } else { 90 | ((*on_error) 91 | .execute_function 92 | .expect("execute_function is a function"))( 93 | on_error, 94 | std::ptr::null_mut(), 95 | 0, 96 | std::ptr::null_mut(), 97 | ); 98 | } 99 | 100 | ((*context).exit.expect("exit is a function"))(context); 101 | (*slf).done_callback = None; 102 | } else { 103 | log::warn!("pdf print is done but callback wasn't set?!"); 104 | } 105 | } 106 | 107 | unsafe extern "C" fn execute( 108 | slf: *mut cef_v8handler_t, 109 | name: *const cef_string_t, 110 | _object: *mut cef_v8value_t, 111 | arguments_count: size_t, 112 | arguments: *const *mut cef_v8value_t, 113 | _retval: *mut *mut cef_v8value_t, 114 | _exception: *mut cef_string_t, 115 | ) -> c_int { 116 | // get the name of the function 117 | let chars: *mut u16 = (*name).str_; 118 | let len: usize = (*name).length as usize; 119 | let chars = std::slice::from_raw_parts(chars, len); 120 | let name = std::char::decode_utf16(chars.iter().cloned()) 121 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 122 | .collect::(); 123 | 124 | if name == "printToPDF" && arguments_count == 3 { 125 | log::debug!("printing!"); 126 | 127 | // get the path argument 128 | let arg_path: *mut cef_v8value_t = *arguments; 129 | let is_string = ((*arg_path).is_string.expect("is_string is a function"))(arg_path) == 1; 130 | if !is_string { 131 | log::warn!("path argument isn't a string!"); 132 | return 0; 133 | } 134 | 135 | // get the onDone argument 136 | let arg_on_done: *mut cef_v8value_t = *(arguments.offset(1)); 137 | let is_function = ((*arg_on_done) 138 | .is_function 139 | .expect("is_function is a function"))(arg_on_done) 140 | == 1; 141 | if !is_function { 142 | log::warn!("onDone argument isn't a function!"); 143 | return 0; 144 | } 145 | 146 | // get the onError argument 147 | let arg_on_error: *mut cef_v8value_t = *(arguments.offset(2)); 148 | let is_function = ((*arg_on_error) 149 | .is_function 150 | .expect("is_function is a function"))(arg_on_error) 151 | == 1; 152 | if !is_function { 153 | log::warn!("onError argument isn't a function!"); 154 | return 0; 155 | } 156 | 157 | // get the path as a string 158 | let cef_path: cef_string_userfree_t = 159 | ((*arg_path) 160 | .get_string_value 161 | .expect("get_string_value is a function"))(arg_path); 162 | 163 | // now send an IPC message to the frame process telling it to print 164 | let _self = slf as *mut V8PDFPrintHandler; 165 | if let Some(browser) = (*_self).browser { 166 | let frame = (*browser) 167 | .get_main_frame 168 | .expect("get_main_frame is a function")(browser); 169 | 170 | // convert the message name to a CEF string 171 | let mut cef_message_name = cef_string_t::default(); 172 | let message_name = "print_to_pdf".as_bytes(); 173 | let message_name = std::ffi::CString::new(message_name).unwrap(); 174 | super::bindings::cef_string_utf8_to_utf16( 175 | message_name.as_ptr(), 176 | message_name.to_bytes().len() as u64, 177 | &mut cef_message_name, 178 | ); 179 | 180 | // store our callback to onDone 181 | let context = cef_v8context_get_current_context(); 182 | (*_self).done_callback = Some((context, arg_on_done, arg_on_error)); 183 | 184 | // build the message 185 | let message = super::bindings::cef_process_message_create(&cef_message_name); 186 | let args = ((*message) 187 | .get_argument_list 188 | .expect("get_argument_list is a function"))(message); 189 | ((*args).set_size.expect("set_size is a function"))(args, 1); 190 | ((*args).set_string.expect("set_string is a function"))(args, 0, cef_path); 191 | 192 | // send the message 193 | ((*frame) 194 | .send_process_message 195 | .expect("send_process_message is a function"))( 196 | frame, 197 | super::bindings::cef_process_id_t_PID_BROWSER, 198 | message, 199 | ); 200 | } else { 201 | log::error!("browser isn't set!"); 202 | } 203 | 204 | cef_string_userfree_utf16_free(cef_path); 205 | 1 206 | } else { 207 | log::warn!( 208 | "unrecognized function: `{}` with {} args, skipping", 209 | name, 210 | arguments_count 211 | ); 212 | 0 213 | } 214 | } 215 | 216 | pub fn allocate() -> *mut V8PDFPrintHandler { 217 | let handler = V8PDFPrintHandler { 218 | v8_handler: cef_v8handler_t { 219 | base: cef_base_ref_counted_t { 220 | size: size_of::() as u64, 221 | add_ref: Some(add_ref), 222 | release: Some(release), 223 | has_one_ref: Some(has_one_ref), 224 | has_at_least_one_ref: Some(has_at_least_one_ref), 225 | }, 226 | execute: Some(execute), 227 | }, 228 | ref_count: AtomicUsize::new(1), 229 | browser: None, 230 | done_callback: None, 231 | }; 232 | 233 | Box::into_raw(Box::from(handler)) 234 | } 235 | 236 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 237 | let v8_handler = base as *mut V8PDFPrintHandler; 238 | unsafe { (*v8_handler).ref_count.fetch_add(1, Ordering::SeqCst) }; 239 | } 240 | 241 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 242 | let v8_handler = base as *mut V8PDFPrintHandler; 243 | let count = unsafe { (*v8_handler).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 244 | 245 | if count == 0 { 246 | unsafe { 247 | Box::from_raw(v8_handler); 248 | } 249 | 1 250 | } else { 251 | 0 252 | } 253 | } 254 | 255 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 256 | let v8_handler = base as *mut V8PDFPrintHandler; 257 | let count = unsafe { (*v8_handler).ref_count.load(Ordering::SeqCst) }; 258 | if count == 1 { 259 | 1 260 | } else { 261 | 0 262 | } 263 | } 264 | 265 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 266 | let v8_handler = base as *mut V8PDFPrintHandler; 267 | let count = unsafe { (*v8_handler).ref_count.load(Ordering::SeqCst) }; 268 | if count >= 1 { 269 | 1 270 | } else { 271 | 0 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /src/imp/v8_file_dialog_handler.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_process_message_t, cef_string_t, 7 | cef_string_userfree_t, cef_string_userfree_utf16_free, cef_v8context_get_current_context, 8 | cef_v8context_t, cef_v8handler_t, cef_v8value_create_string, cef_v8value_t, size_t, 9 | }; 10 | 11 | #[derive(Debug)] 12 | pub enum FileDialogMode { 13 | Open, 14 | Save, 15 | } 16 | 17 | #[repr(C)] 18 | pub struct V8FileDialogHandler { 19 | v8_handler: cef_v8handler_t, 20 | ref_count: AtomicUsize, 21 | pub browser: Option<*mut cef_browser_t>, 22 | pub done_callback: Option<(*mut cef_v8context_t, *mut cef_v8value_t, *mut cef_v8value_t)>, 23 | } 24 | 25 | const CODE: &str = r#" 26 | var cef; 27 | if(!cef) cef = {}; 28 | (function() { 29 | cef.saveFileDialog = function(title, defaultFileName, filter) { 30 | native function saveFileDialog(title, defaultFileName, filter, onDone, onError); 31 | return new Promise((resolve, reject) => { 32 | console.debug("running native saveFileDialog..."); 33 | saveFileDialog(title, defaultFileName, filter, resolve, reject); 34 | }); 35 | }; 36 | cef.openFileDialog = function(title, defaultFileName, filter) { 37 | native function openFileDialog(title, defaultFileName, filter, onDone, onError); 38 | return new Promise((resolve, reject) => { 39 | console.debug("running native openFileDialog..."); 40 | openFileDialog(title, defaultFileName, filter, resolve, reject); 41 | }); 42 | }; 43 | console.info("registered saveFileDialog and openFileDialog CEF extensions"); 44 | })(); 45 | "#; 46 | 47 | pub unsafe fn register_extension(extension: *mut V8FileDialogHandler) { 48 | use super::bindings::{cef_register_extension, cef_string_utf8_to_utf16}; 49 | use std::ffi::CString; 50 | let code = CODE.as_bytes(); 51 | let code = CString::new(code).unwrap(); 52 | let mut cef_code = cef_string_t::default(); 53 | cef_string_utf8_to_utf16(code.as_ptr(), code.to_bytes().len() as u64, &mut cef_code); 54 | 55 | let extension_name = "CEF File Dialogs"; 56 | let extension_name = extension_name.as_bytes(); 57 | let extension_name = CString::new(extension_name).unwrap(); 58 | let mut cef_extension_name = cef_string_t::default(); 59 | cef_string_utf8_to_utf16( 60 | extension_name.as_ptr(), 61 | extension_name.to_bytes().len() as u64, 62 | &mut cef_extension_name, 63 | ); 64 | 65 | cef_register_extension( 66 | &cef_extension_name, 67 | &cef_code, 68 | extension as *mut cef_v8handler_t, 69 | ); 70 | log::debug!("registered file dialogs extension"); 71 | } 72 | 73 | pub unsafe fn process_message( 74 | slf: *mut V8FileDialogHandler, 75 | message_name: &str, 76 | message: *mut cef_process_message_t, 77 | ) -> bool { 78 | if message_name != "run_file_dialog_done" { 79 | return false; 80 | } 81 | 82 | log::debug!("processing returned message"); 83 | let args = ((*message) 84 | .get_argument_list 85 | .expect("get_argument_list is a function"))(message); 86 | let size = (*args).get_size.expect("get_size is a function")(args); 87 | if size < 1 { 88 | on_file_dialog_done(slf, None); 89 | } else { 90 | let cef_path: cef_string_userfree_t = 91 | (*args).get_string.expect("get_string is a function")(args, 0); 92 | on_file_dialog_done(slf, Some(cef_path)); 93 | cef_string_userfree_utf16_free(cef_path); 94 | } 95 | 96 | true 97 | } 98 | 99 | unsafe fn on_file_dialog_done(slf: *mut V8FileDialogHandler, path: Option<*const cef_string_t>) { 100 | log::debug!("file dialog done"); 101 | if let Some((context, on_success, on_error)) = (*slf).done_callback { 102 | ((*context).enter.expect("enter is a function"))(context); 103 | 104 | if let Some(path) = path { 105 | // store the path as a string in v8 106 | let v8_path = cef_v8value_create_string(path); 107 | // and call success, with a single argument that is the path! 108 | log::debug!("successfully ran file dialog!"); 109 | ((*on_success) 110 | .execute_function 111 | .expect("execute_function is a function"))( 112 | on_success, 113 | std::ptr::null_mut(), 114 | 1, 115 | &v8_path, 116 | ); 117 | } else { 118 | // if the path was None, the user cancelled; report that as an error to JS 119 | log::debug!("user cancelled file dialog!"); 120 | ((*on_error) 121 | .execute_function 122 | .expect("execute_function is a function"))( 123 | on_error, 124 | std::ptr::null_mut(), 125 | 0, 126 | std::ptr::null_mut(), 127 | ); 128 | } 129 | 130 | ((*context).exit.expect("exit is a function"))(context); 131 | (*slf).done_callback = None; 132 | } else { 133 | log::warn!("file dialog is done but callback wasn't set?!"); 134 | } 135 | } 136 | 137 | unsafe extern "C" fn execute( 138 | slf: *mut cef_v8handler_t, 139 | name: *const cef_string_t, 140 | _object: *mut cef_v8value_t, 141 | arguments_count: size_t, 142 | arguments: *const *mut cef_v8value_t, 143 | _retval: *mut *mut cef_v8value_t, 144 | _exception: *mut cef_string_t, 145 | ) -> c_int { 146 | // get the name of the function 147 | let chars: *mut u16 = (*name).str_; 148 | let len: usize = (*name).length as usize; 149 | let chars = std::slice::from_raw_parts(chars, len); 150 | let name = std::char::decode_utf16(chars.iter().cloned()) 151 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 152 | .collect::(); 153 | 154 | log::debug!( 155 | "native call to function: {} with {} arguments", 156 | name, 157 | arguments_count 158 | ); 159 | if (name == "saveFileDialog" || name == "openFileDialog") && arguments_count == 5 { 160 | // get the title argument 161 | let arg_title: *mut cef_v8value_t = *arguments.offset(0); 162 | let is_string = ((*arg_title).is_string.expect("is_string is a function"))(arg_title) == 1; 163 | if !is_string { 164 | log::warn!("title argument isn't a string!"); 165 | return 0; 166 | } 167 | 168 | // get the file name argument 169 | let arg_file_name: *mut cef_v8value_t = *arguments.offset(1); 170 | let is_string: bool = 171 | ((*arg_file_name).is_string.expect("is_string is a function"))(arg_file_name) == 1; 172 | let arg_file_name_is_null: bool = 173 | ((*arg_file_name).is_null.expect("is_null is a function"))(arg_file_name) == 1; 174 | if !arg_file_name_is_null && !is_string { 175 | log::error!("file name argument isn't a string, nor is it null!"); 176 | return 0; 177 | } 178 | 179 | // get the filter argument 180 | let arg_filter: *mut cef_v8value_t = *arguments.offset(2); 181 | let is_string = 182 | ((*arg_filter).is_string.expect("is_string is a function"))(arg_filter) == 1; 183 | if !is_string { 184 | log::warn!("filter argument isn't a string!"); 185 | return 0; 186 | } 187 | 188 | // get the onDone argument 189 | let arg_on_done: *mut cef_v8value_t = *arguments.offset(3); 190 | let is_function = ((*arg_on_done) 191 | .is_function 192 | .expect("is_function is a function"))(arg_on_done) 193 | == 1; 194 | if !is_function { 195 | log::warn!("onDone argument isn't a function!"); 196 | return 0; 197 | } 198 | 199 | // get the onError argument 200 | let arg_on_error: *mut cef_v8value_t = *arguments.offset(4); 201 | let is_function = ((*arg_on_error) 202 | .is_function 203 | .expect("is_function is a function"))(arg_on_error) 204 | == 1; 205 | if !is_function { 206 | log::warn!("onError argument isn't a function!"); 207 | return 0; 208 | } 209 | 210 | // get the v8 strings as cef strings 211 | let cef_title: cef_string_userfree_t = 212 | ((*arg_title) 213 | .get_string_value 214 | .expect("get_string_value is a function"))(arg_title); 215 | let cef_file_name: cef_string_userfree_t = if arg_file_name_is_null { 216 | super::bindings::cef_string_userfree_utf16_alloc() 217 | } else { 218 | ((*arg_file_name) 219 | .get_string_value 220 | .expect("get_string_value is a function"))(arg_file_name) 221 | }; 222 | let cef_filter: cef_string_userfree_t = 223 | ((*arg_filter) 224 | .get_string_value 225 | .expect("get_string_value is a function"))(arg_filter); 226 | 227 | log::debug!("extracted arguments..."); 228 | 229 | // now send an IPC message to the frame process telling it to open the file dialog 230 | let _self = slf as *mut V8FileDialogHandler; 231 | log::debug!("{} called from JS", name); 232 | if let Some(browser) = (*_self).browser { 233 | let frame = (*browser) 234 | .get_main_frame 235 | .expect("get_main_frame is a function")(browser); 236 | 237 | // convert the message name to a CEF string 238 | let mut cef_message_name = cef_string_t::default(); 239 | let message_name = match name.as_ref() { 240 | "openFileDialog" => "open_file_dialog".as_bytes(), 241 | "saveFileDialog" => "save_file_dialog".as_bytes(), 242 | _ => unreachable!(), 243 | }; 244 | let message_name = std::ffi::CString::new(message_name).unwrap(); 245 | super::bindings::cef_string_utf8_to_utf16( 246 | message_name.as_ptr(), 247 | message_name.to_bytes().len() as u64, 248 | &mut cef_message_name, 249 | ); 250 | log::debug!("generated message name"); 251 | 252 | // store our callback to onDone 253 | let context = cef_v8context_get_current_context(); 254 | (*_self).done_callback = Some((context, arg_on_done, arg_on_error)); 255 | log::debug!("stored done callback"); 256 | 257 | // build the message 258 | let message = super::bindings::cef_process_message_create(&cef_message_name); 259 | let args = ((*message) 260 | .get_argument_list 261 | .expect("get_argument_list is a function"))(message); 262 | ((*args).set_size.expect("set_size is a function"))(args, 3); 263 | ((*args).set_string.expect("set_string is a function"))(args, 0, cef_title); 264 | ((*args).set_string.expect("set_string is a function"))(args, 1, cef_file_name); 265 | ((*args).set_string.expect("set_string is a function"))(args, 2, cef_filter); 266 | log::debug!("built IPC message"); 267 | 268 | // send the message 269 | ((*frame) 270 | .send_process_message 271 | .expect("send_process_message is a function"))( 272 | frame, 273 | super::bindings::cef_process_id_t_PID_BROWSER, 274 | message, 275 | ); 276 | log::debug!("sent IPC message"); 277 | } else { 278 | log::error!("browser isn't set!"); 279 | } 280 | 281 | cef_string_userfree_utf16_free(cef_title); 282 | cef_string_userfree_utf16_free(cef_file_name); 283 | cef_string_userfree_utf16_free(cef_filter); 284 | 1 285 | } else { 286 | log::warn!( 287 | "unrecognized function: `{}` with {} args, skipping", 288 | name, 289 | arguments_count 290 | ); 291 | 0 292 | } 293 | } 294 | 295 | pub fn allocate() -> *mut V8FileDialogHandler { 296 | let handler = V8FileDialogHandler { 297 | v8_handler: cef_v8handler_t { 298 | base: cef_base_ref_counted_t { 299 | size: size_of::() as u64, 300 | add_ref: Some(add_ref), 301 | release: Some(release), 302 | has_one_ref: Some(has_one_ref), 303 | has_at_least_one_ref: Some(has_at_least_one_ref), 304 | }, 305 | execute: Some(execute), 306 | }, 307 | ref_count: AtomicUsize::new(1), 308 | browser: None, 309 | done_callback: None, 310 | }; 311 | 312 | Box::into_raw(Box::from(handler)) 313 | } 314 | 315 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 316 | let v8_handler = base as *mut V8FileDialogHandler; 317 | unsafe { (*v8_handler).ref_count.fetch_add(1, Ordering::SeqCst) }; 318 | } 319 | 320 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 321 | let v8_handler = base as *mut V8FileDialogHandler; 322 | let count = unsafe { (*v8_handler).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 323 | 324 | if count == 0 { 325 | unsafe { 326 | log::debug!("dropping file dialog v8 handler!"); 327 | Box::from_raw(v8_handler); 328 | } 329 | 1 330 | } else { 331 | 0 332 | } 333 | } 334 | 335 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 336 | let v8_handler = base as *mut V8FileDialogHandler; 337 | let count = unsafe { (*v8_handler).ref_count.load(Ordering::SeqCst) }; 338 | if count == 1 { 339 | 1 340 | } else { 341 | 0 342 | } 343 | } 344 | 345 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 346 | let v8_handler = base as *mut V8FileDialogHandler; 347 | let count = unsafe { (*v8_handler).ref_count.load(Ordering::SeqCst) }; 348 | if count >= 1 { 349 | 1 350 | } else { 351 | 0 352 | } 353 | } 354 | -------------------------------------------------------------------------------- /src/imp/client.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | use std::os::raw::c_int; 3 | use std::sync::atomic::{AtomicUsize, Ordering}; 4 | 5 | use super::bindings::{ 6 | cef_base_ref_counted_t, cef_browser_t, cef_client_t, cef_context_menu_handler_t, 7 | cef_display_handler_t, cef_frame_t, cef_life_span_handler_t, cef_process_id_t, 8 | cef_process_message_t, cef_request_handler_t, cef_string_t, cef_string_userfree_t, 9 | cef_string_userfree_utf16_free, cef_window_t, 10 | }; 11 | use super::context_menu_handler::{self, ContextMenuHandler}; 12 | use super::display_handler::{self, DisplayHandler}; 13 | use super::life_span_handler::{self, LifeSpanHandler}; 14 | use super::request_handler::{self, RequestHandler}; 15 | 16 | #[repr(C)] 17 | pub struct Client { 18 | client: cef_client_t, 19 | ref_count: AtomicUsize, 20 | life_span_handler: *mut LifeSpanHandler, 21 | context_menu_handler: *mut ContextMenuHandler, 22 | request_handler: *mut RequestHandler, 23 | display_handler: *mut DisplayHandler, 24 | } 25 | 26 | impl Client { 27 | pub fn inc_ref(&self) { 28 | self.ref_count.fetch_add(1, Ordering::SeqCst); 29 | } 30 | } 31 | 32 | extern "C" fn get_life_span_handler(slf: *mut cef_client_t) -> *mut cef_life_span_handler_t { 33 | let client = slf as *mut Client; 34 | let handler = unsafe { (*client).life_span_handler }; 35 | unsafe { (*handler).inc_ref() }; 36 | handler as *mut cef_life_span_handler_t 37 | } 38 | 39 | extern "C" fn get_context_menu_handler(slf: *mut cef_client_t) -> *mut cef_context_menu_handler_t { 40 | let client = slf as *mut Client; 41 | let handler = unsafe { (*client).context_menu_handler }; 42 | unsafe { (*handler).inc_ref() }; 43 | handler as *mut cef_context_menu_handler_t 44 | } 45 | 46 | extern "C" fn get_request_handler(slf: *mut cef_client_t) -> *mut cef_request_handler_t { 47 | let client = slf as *mut Client; 48 | let handler = unsafe { (*client).request_handler }; 49 | unsafe { (*handler).inc_ref() }; 50 | handler as *mut cef_request_handler_t 51 | } 52 | 53 | extern "C" fn get_display_handler(slf: *mut cef_client_t) -> *mut cef_display_handler_t { 54 | let client = slf as *mut Client; 55 | let handler = unsafe { (*client).display_handler }; 56 | unsafe { (*handler).inc_ref() }; 57 | handler as *mut cef_display_handler_t 58 | } 59 | 60 | unsafe extern "C" fn on_process_message_received( 61 | _slf: *mut cef_client_t, 62 | browser: *mut cef_browser_t, 63 | frame: *mut cef_frame_t, 64 | _source_process: cef_process_id_t, 65 | message: *mut cef_process_message_t, 66 | ) -> c_int { 67 | let cef_message_name: cef_string_userfree_t = 68 | ((*message).get_name.expect("get_name is a function"))(message); 69 | let chars: *mut u16 = (*cef_message_name).str_; 70 | let len: usize = (*cef_message_name).length as usize; 71 | let chars = std::slice::from_raw_parts(chars, len); 72 | let message_name = std::char::decode_utf16(chars.iter().cloned()) 73 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 74 | .collect::(); 75 | cef_string_userfree_utf16_free(cef_message_name); 76 | 77 | log::debug!("browser process recieved `{}` message", message_name); 78 | if message_name == "print_to_pdf" { 79 | log::debug!("print to pdf message!"); 80 | // get the path 81 | let args = ((*message) 82 | .get_argument_list 83 | .expect("get_argument_list is a function"))(message); 84 | let cef_path: cef_string_userfree_t = 85 | ((*args).get_string.expect("get_string is a function"))(args, 0); 86 | let chars: *mut u16 = (*cef_path).str_; 87 | let len: usize = (*cef_path).length as usize; 88 | let chars = std::slice::from_raw_parts(chars, len); 89 | let path = std::char::decode_utf16(chars.iter().cloned()) 90 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 91 | .collect::(); 92 | cef_string_userfree_utf16_free(cef_path); 93 | 94 | super::browser::print_to_pdf( 95 | browser, 96 | path, 97 | Some(Box::from(move |ok| { 98 | // now send an IPC message back to the renderer 99 | // convert the message name to a CEF string 100 | let mut cef_message_name = cef_string_t::default(); 101 | let message_name = "print_to_pdf_done".as_bytes(); 102 | let message_name = std::ffi::CString::new(message_name).unwrap(); 103 | super::bindings::cef_string_utf8_to_utf16( 104 | message_name.as_ptr(), 105 | message_name.to_bytes().len() as u64, 106 | &mut cef_message_name, 107 | ); 108 | 109 | // build the message 110 | let message = super::bindings::cef_process_message_create(&cef_message_name); 111 | let args = ((*message) 112 | .get_argument_list 113 | .expect("get_argument_list is a function"))(message); 114 | ((*args).set_size.expect("set_size is a function"))(args, 1); 115 | ((*args).set_bool.expect("set_bool is a function"))(args, 0, ok as i32); 116 | 117 | // send the message 118 | ((*frame) 119 | .send_process_message 120 | .expect("send_process_message is a function"))( 121 | frame, 122 | super::bindings::cef_process_id_t_PID_RENDERER, 123 | message, 124 | ); 125 | })), 126 | ); 127 | 128 | 1 129 | } else if message_name == "save_file_dialog" || message_name == "open_file_dialog" { 130 | let args = ((*message) 131 | .get_argument_list 132 | .expect("get_argument_list is a function"))(message); 133 | 134 | let num_args = (*args).get_size.expect("get_size is a function")(args); 135 | debug_assert_eq!(num_args, 3); 136 | 137 | // get the title 138 | let cef_title: cef_string_userfree_t = 139 | ((*args).get_string.expect("get_string is a function"))(args, 0); 140 | let title: String = if cef_title == std::ptr::null_mut() { 141 | let chars: *mut u16 = (*cef_title).str_; 142 | let len: usize = (*cef_title).length as usize; 143 | let chars = std::slice::from_raw_parts(chars, len); 144 | let title = std::char::decode_utf16(chars.iter().cloned()) 145 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 146 | .collect::(); 147 | cef_string_userfree_utf16_free(cef_title); 148 | title 149 | } else { 150 | "".to_owned() 151 | }; 152 | 153 | // get the initial_file_name 154 | let cef_initial_file_name: cef_string_userfree_t = 155 | ((*args).get_string.expect("get_string is a function"))(args, 1); 156 | let initial_file_name: String = if cef_initial_file_name == std::ptr::null_mut() { 157 | "".to_owned() 158 | } else { 159 | let chars: *mut u16 = (*cef_initial_file_name).str_; 160 | let len: usize = (*cef_initial_file_name).length as usize; 161 | let chars = std::slice::from_raw_parts(chars, len); 162 | let initial_file_name = std::char::decode_utf16(chars.iter().cloned()) 163 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 164 | .collect::(); 165 | cef_string_userfree_utf16_free(cef_initial_file_name); 166 | initial_file_name 167 | }; 168 | 169 | // get the filter 170 | let cef_filter: cef_string_userfree_t = 171 | ((*args).get_string.expect("get_string is a function"))(args, 2); 172 | let filter: String = if cef_filter != std::ptr::null_mut() { 173 | let chars: *mut u16 = (*cef_filter).str_; 174 | let len: usize = (*cef_filter).length as usize; 175 | let chars = std::slice::from_raw_parts(chars, len); 176 | let filter = std::char::decode_utf16(chars.iter().cloned()) 177 | .map(|r| r.unwrap_or(std::char::REPLACEMENT_CHARACTER)) 178 | .collect::(); 179 | cef_string_userfree_utf16_free(cef_filter); 180 | filter 181 | } else { 182 | "".to_owned() 183 | }; 184 | log::debug!( 185 | "{} with title: “{}”; initial_file_name “{}”; filter: “{}”", 186 | message_name, 187 | title, 188 | initial_file_name, 189 | filter 190 | ); 191 | 192 | log::debug!( 193 | "title: {}, initial file name: {}, filter: {}", 194 | title, 195 | initial_file_name, 196 | filter 197 | ); 198 | super::browser::run_file_dialog( 199 | browser, 200 | match message_name.as_ref() { 201 | "open_file_dialog" => super::v8_file_dialog_handler::FileDialogMode::Open, 202 | "save_file_dialog" => super::v8_file_dialog_handler::FileDialogMode::Save, 203 | _ => unreachable!(), 204 | }, 205 | title, 206 | initial_file_name, 207 | filter, 208 | Some(Box::from(move |path: Option| { 209 | log::debug!("client save callback, path: {:?}", path); 210 | // now send an IPC message back to the renderer 211 | // convert the message name to a CEF string 212 | let mut cef_message_name = cef_string_t::default(); 213 | let message_name = "run_file_dialog_done".as_bytes(); 214 | let message_name = std::ffi::CString::new(message_name).unwrap(); 215 | super::bindings::cef_string_utf8_to_utf16( 216 | message_name.as_ptr(), 217 | message_name.to_bytes().len() as u64, 218 | &mut cef_message_name, 219 | ); 220 | 221 | // build the message 222 | let message = super::bindings::cef_process_message_create(&cef_message_name); 223 | let args = ((*message) 224 | .get_argument_list 225 | .expect("get_argument_list is a function"))(message); 226 | if let Some(path) = path { 227 | ((*args).set_size.expect("set_size is a function"))(args, 1); 228 | 229 | let mut cef_path = cef_string_t::default(); 230 | let path = path.display().to_string(); 231 | let path = path.as_bytes(); 232 | let path = std::ffi::CString::new(path).unwrap(); 233 | super::bindings::cef_string_utf8_to_utf16( 234 | path.as_ptr(), 235 | path.to_bytes().len() as u64, 236 | &mut cef_path, 237 | ); 238 | ((*args).set_string.expect("set_string is a function"))(args, 0, &cef_path); 239 | } else { 240 | ((*args).set_size.expect("set_size is a function"))(args, 0); 241 | } 242 | 243 | // and finally send the message 244 | log::debug!("returning path to JS..."); 245 | ((*frame) 246 | .send_process_message 247 | .expect("send_process_message is a function"))( 248 | frame, 249 | super::bindings::cef_process_id_t_PID_RENDERER, 250 | message, 251 | ); 252 | })), 253 | ); 254 | 255 | 1 256 | } else { 257 | log::debug!("unhandled IPC message: {}", message_name); 258 | 0 259 | } 260 | } 261 | 262 | pub fn allocate(window: *mut cef_window_t) -> *mut Client { 263 | let client = Client { 264 | client: cef_client_t { 265 | base: cef_base_ref_counted_t { 266 | size: size_of::() as u64, 267 | add_ref: Some(add_ref), 268 | release: Some(release), 269 | has_one_ref: Some(has_one_ref), 270 | has_at_least_one_ref: Some(has_at_least_one_ref), 271 | }, 272 | get_context_menu_handler: Some(get_context_menu_handler), 273 | get_dialog_handler: None, 274 | get_display_handler: Some(get_display_handler), 275 | get_download_handler: None, 276 | get_drag_handler: None, 277 | get_find_handler: None, 278 | get_focus_handler: None, 279 | get_jsdialog_handler: None, 280 | get_keyboard_handler: None, 281 | get_life_span_handler: Some(get_life_span_handler), 282 | get_load_handler: None, 283 | get_render_handler: None, 284 | get_request_handler: Some(get_request_handler), 285 | on_process_message_received: Some(on_process_message_received), 286 | }, 287 | ref_count: AtomicUsize::new(1), 288 | life_span_handler: life_span_handler::allocate(), 289 | context_menu_handler: context_menu_handler::allocate(), 290 | request_handler: request_handler::allocate(), 291 | display_handler: display_handler::allocate(window), 292 | }; 293 | 294 | Box::into_raw(Box::from(client)) 295 | } 296 | 297 | extern "C" fn add_ref(base: *mut cef_base_ref_counted_t) { 298 | let client = base as *mut Client; 299 | unsafe { 300 | (*client).ref_count.fetch_add(1, Ordering::SeqCst); 301 | } 302 | } 303 | 304 | extern "C" fn release(base: *mut cef_base_ref_counted_t) -> c_int { 305 | let client = base as *mut Client; 306 | let count = unsafe { (*client).ref_count.fetch_sub(1, Ordering::SeqCst) - 1 }; 307 | 308 | if count == 0 { 309 | unsafe { 310 | Box::from_raw(client); 311 | // TODO: free our handlers here too? 312 | } 313 | 1 314 | } else { 315 | 0 316 | } 317 | } 318 | 319 | extern "C" fn has_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 320 | let client = base as *mut Client; 321 | let count = unsafe { (*client).ref_count.load(Ordering::SeqCst) }; 322 | if count == 1 { 323 | 1 324 | } else { 325 | 0 326 | } 327 | } 328 | 329 | extern "C" fn has_at_least_one_ref(base: *mut cef_base_ref_counted_t) -> c_int { 330 | let client = base as *mut Client; 331 | let count = unsafe { (*client).ref_count.load(Ordering::SeqCst) }; 332 | if count >= 1 { 333 | 1 334 | } else { 335 | 0 336 | } 337 | } 338 | --------------------------------------------------------------------------------